如何使用SwiftUI具有动态视图列表

iOS*_*com 13 ios swift swiftui xcode11

我可以做一个静态列表

List {
   View1()
   View2()
}
Run Code Online (Sandbox Code Playgroud)

但是,如何从数组中动态生成元素列表?我尝试了以下操作,但出现错误:包含控制流语句的闭包不能与函数生成器“ ViewBuilder”一起使用

    let elements: [Any] = [View1.self, View2.self]

    List {
       ForEach(0..<elements.count) { index in
          if let _ = elements[index] as? View1 {
             View1()
          } else {
             View2()
          }
    }
}
Run Code Online (Sandbox Code Playgroud)

有什么解决办法吗?我要完成的工作是一个列表,其中包含不是静态输入的动态元素集。

iOS*_*com 8

看起来答案与将我的视图包装在其中有关 AnyView

struct ContentView : View {
    var myTypes: [Any] = [View1.self, View2.self]
    var body: some View {
        List {
            ForEach(0..<myTypes.count) { index in
                self.buildView(types: self.myTypes, index: index)
            }
        }
    }

    func buildView(types: [Any], index: Int) -> AnyView {
        switch types[index].self {
           case is View1.Type: return AnyView( View1() )
           case is View1.Type: return AnyView( View2() )
           default: return AnyView(EmptyView())
        }
    }
Run Code Online (Sandbox Code Playgroud)

这样,我现在可以从服务器获取视图数据并进行组合。而且,仅在需要时才实例化它们。


Mat*_*ini 7

if/let流控制语句不能在@ViewBuilder块中使用。

这些特殊块内的流控制语句被转换为结构。

例如

if (someBool) {
    View1()
} else {
    View2()
}
Run Code Online (Sandbox Code Playgroud)

被翻译成ConditionalValue<View1, View2>.

并非所有流控制语句都在这些块中可用,即switch,但这在未来可能会发生变化。

在函数构建器进化提案中更多地了解这一点。


在您的具体示例中,您可以按如下方式重写代码:

struct ContentView : View {

    let elements: [Any] = [View1.self, View2.self]

    var body: some View {
        List {
            ForEach(0..<elements.count) { index in
                if self.elements[index] is View1 {
                    View1()
                } else {
                    View2()
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以使用子视图的动态列表,但需要小心类型和实例化。作为参考,这是一个动态“汉堡包”的演示,github/swiftui_hamburger。

// Pages View to select current page
/// This could be refactored into the top level
struct Pages: View {
    @Binding var currentPage: Int
    var pageArray: [AnyView]

    var body: AnyView {
        return pageArray[currentPage]
    }
}

// Top Level View
/// Create two sub-views which, critially, need to be cast to AnyView() structs
/// Pages View then dynamically presents the subviews, based on currentPage state
struct ContentView: View {
    @State var currentPage: Int = 0

    let page0 = AnyView(
        NavigationView {
            VStack {
                Text("Page Menu").color(.black)

                List(["1", "2", "3", "4", "5"].identified(by: \.self)) { row in
                    Text(row)
                }.navigationBarTitle(Text("A Page"), displayMode: .large)
            }
        }
    )

    let page1 = AnyView(
        NavigationView {
            VStack {
                Text("Another Page Menu").color(.black)

                List(["A", "B", "C", "D", "E"].identified(by: \.self)) { row in
                    Text(row)
                }.navigationBarTitle(Text("A Second Page"), displayMode: .large)
            }
        }
    )

    var body: some View {
        let pageArray: [AnyView] = [page0, page1]

        return Pages(currentPage: self.$currentPage, pageArray: pageArray)

    }
}
Run Code Online (Sandbox Code Playgroud)


fuz*_*uzz 5

您可以通过多态性来做到这一点:

struct View1: View {
    var body: some View {
        Text("View1")
    }
}

struct View2: View {
    var body: some View {
        Text("View2")
    }
}

class ViewBase: Identifiable {
    func showView() -> AnyView {
        AnyView(EmptyView())
    }
}

class AnyView1: ViewBase {
    override func showView() -> AnyView {
        AnyView(View1())
    }
}

class AnyView2: ViewBase {
    override func showView() -> AnyView {
        AnyView(View2())
    }
}

struct ContentView: View {
    let views: [ViewBase] = [
        AnyView1(),
        AnyView2()
    ]

    var body: some View {
        List(self.views) { view in
            view.showView()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)