SwiftUI - 自动在 `ForEach` 的每个元素之间添加分隔线

ahe*_*eze 3 generics ios swift swift-keypath swiftui

我使用 aForEach来显示数组的内容,然后通过检查元素索引手动显示每个元素之间的分隔线。这是我的代码:

\n
struct ContentView: View {\n    let animals = ["Apple", "Bear", "Cat", "Dog", "Elephant"]\n\n    var body: some View {\n        VStack {\n            /// array of tuples containing each element\'s index and the element itself\n            let enumerated = Array(zip(animals.indices, animals))\n            ForEach(enumerated, id: \\.1) { index, animal in\n                Text(animal)\n\n                /// add a divider if the element isn\'t the last\n                if index != enumerated.count - 1 {\n                    Divider()\n                        .background(.blue)\n                }\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n

Stack of text with dividers in between

\n

这是可行的,但我想要一种在各处自动添加分隔线的方法,而无需Array(zip(animals.indices, animals))每次都编写分隔线。这是我到目前为止所拥有的:

\n
struct ForEachDividerView<Data, Content>: View where Data: RandomAccessCollection, Data.Element: Hashable, Content: View {\n    var data: Data\n    var content: (Data.Element) -> Content\n\n    var body: some View {\n        let enumerated = Array(zip(data.indices, data))\n        ForEach(enumerated, id: \\.1) { index, data in\n\n            /// generate the view\n            content(data)\n\n            /// add a divider if the element isn\'t the last\n            if let index = index as? Int, index != enumerated.count - 1 {\n                Divider()\n                    .background(.blue)\n            }\n        }\n    }\n}\n\n/// usage\nForEachDividerView(data: animals) { animal in\n    Text(animal)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这非常有效,隔离了所有样板zip代码并且仍然得到相同的结果。然而,这只是因为animals是 s 的数组String,它符合Hashable\xe2\x80\x94 如果我的数组中的元素不符合Hashable,它将不起作用:

\n
struct Person {\n    var name: String\n}\n\nstruct ContentView: View {\n    let people: [Person] = [\n        .init(name: "Anna"),\n        .init(name: "Bob"),\n        .init(name: "Chris")\n    ]\n\n    var body: some View {\n        VStack {\n\n            /// Error! Generic struct \'ForEachDividerView\' requires that \'Person\' conform to \'Hashable\'\n            ForEachDividerView(data: people) { person in\n                Text(person.name)\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这就是为什么 SwiftUIForEach附带了一个额外的初始化程序 ,init(_:id:content:)它采用自定义键路径来提取 ID。我想在我的 中利用这个初始化程序ForEachDividerView,但我无法弄清楚。这是我尝试过的:

\n
struct ForEachDividerView<Data, Content, ID>: View where Data: RandomAccessCollection, ID: Hashable, Content: View {\n    var data: Data\n    var id: KeyPath<Data.Element, ID>\n    var content: (Data.Element) -> Content\n\n    var body: some View {\n        let enumerated = Array(zip(data.indices, data))\n\n        /// Error! Invalid component of Swift key path\n        ForEach(enumerated, id: \\.1.appending(path: id)) { index, data in\n\n            content(data)\n\n            if let index = index as? Int, index != enumerated.count - 1 {\n                Divider()\n                    .background(.blue)\n            }\n        }\n    }\n}\n\n\n/// at least this part works...\nForEachDividerView(data: people, id: \\.name) { person in\n    Text(person.name)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我尝试使用将appending(path:)第一个关键路径(从 中提取元素)与第二个关键路径(从元素中enumerated获取属性)组合起来,但我得到了。HashableInvalid component of Swift key path

\n

ForEach即使元素不符合 ,如何在 a 的元素之间自动添加分隔线Hashable

\n

art*_*has 8

简单的方法

struct ContentView: View {
let animals = ["Apple", "Bear", "Cat", "Dog", "Elephant"]

var body: some View {
    VStack {

        ForEach(animals, id: \.self) { animal in
            Text(animal)

            if animals.last != animal  {
                Divider()
                    .background(.blue)
            }
        }
    }
}
}
Run Code Online (Sandbox Code Playgroud)

通常,动物体内的类型必须是可识别的。在这种情况下,代码将被修改为。

          if animals.last.id != animal.id {...}
Run Code Online (Sandbox Code Playgroud)

这将避免任何同等的要求/实施


Rob*_*rty 8

使用评论中提到的文章,我构建了以下内容。它采用一组视图并在它们之间放置一个分隔线。

当视图不是由 a 生成时ForEach,尤其是当有条件地删除一个或多个视图时(例如使用语句if),这也很有用。

struct Divided<Content: View>: View {
    var content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        _VariadicView.Tree(DividedLayout()) {
            content
        }
    }

    struct DividedLayout: _VariadicView_MultiViewRoot {
        @ViewBuilder
        func body(children: _VariadicView.Children) -> some View {
            let last = children.last?.id

            ForEach(children) { child in
                child

                if child.id != last {
                    Divider()
                }
            }
        }
    }
}

struct Divided_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            Divided {
                Text("Alpha")
                Text("Beta")
                Text("Gamma")
            }
        }
        .previewDisplayName("Vertical")

        HStack {
            Divided {
                Text("Alpha")
                Text("Beta")
                Text("Gamma")
            }
        }
        .previewDisplayName("Horizontal")
    }
}
Run Code Online (Sandbox Code Playgroud)