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

时间:2019-06-18 09:13:57

标签: ios swift swiftui xcode11

我可以做一个静态列表

List {
   View1()
   View2()
}

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

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

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

有没有解决的办法? 我要完成的工作是一个包含非静态输入的动态元素的列表。

8 个答案:

答案 0 :(得分:2)

您可以使用动态的子视图列表,但是需要小心类型和实例化。作为参考,这是一个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)

    }
}

答案 1 :(得分:2)

答案似乎与将我的视图包装在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())
        }
    }

有了这个,我现在可以从服务器获取视图数据并进行组合。此外,它们仅在需要时进行实例化。

答案 2 :(得分:0)

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

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

例如

if (someBool) {
    View1()
} else {
    View2()
}

转换为ConditionalValue<View1, View2>

在这些块(即switch)中不是所有的流控制语句都可用,但是将来可能会改变。

function builder evolution proposal中对此有更多的了解。


在您的特定示例中,您可以按以下方式重写代码:

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()
                }
            }
        }
    }
}

答案 3 :(得分:0)

是否可以根据需要返回不同的View

简而言之:排序

swift.org中所述,具有多个 Type 作为不透明类型 不可行 >

  

如果具有不透明返回类型的函数从多个位置返回,则所有可能的返回值都必须具有相同的类型。对于通用函数,该返回类型可以使用该函数的通用类型参数,但仍必须是单个类型。

那么List在静态传递一些不同的视图时如何做到这一点?

列表没有返回不同的类型,它返回填充了某些内容视图的EmptyView。构建器可以围绕传递给它的任何类型的视图构建包装器,但是当您使用越来越多的视图时,它甚至根本无法编译! (例如,尝试传递10个以上的视图并查看会发生什么情况)

enter image description here

如您所见,List的内容是ListCoreCellHost的某种形式,包含视图的子集,证明它只是它所代表的内容的容器。

如果我有大量数据(例如联系人)并想为其填写列表怎么办?

您可以遵循here的规定使用Identifiable或使用identified(by:)函数。

如果任何联系人可以拥有不同的视图怎么办?

当您称他们为联系人时,表示他们是同一个人!您应该考虑使用OOP使其保持一致并使用inheritance优势。但是与UIKit不同的是,SwiftUI是基于结构的。他们不能互相继承。

那是什么解决方案?

必须将要显示的所有视图包装为单一的View类型。 EmptyView的文档尚不足以利用它(目前)。 但是!幸运的是,您可以使用 UIKit

我该如何利用UIKit

  • View1上方实施View2UIKit
  • 使用UIKit定义ContainerView
  • 实施ContainerView接受参数并表示View1View2的方式以及适合的大小。
  • 符合UIViewRepresentable并实现其要求。
  • 制作您的 SwiftUI List以显示ContainerView的列表 所以现在它是一个可以代表多个视图的单一类型

答案 4 :(得分:0)

我找到了比上面的答案更简单的方法。

创建您的自定义视图。

确保您的视图是可识别的

(它告诉SwiftUI通过查看其id属性可以区分ForEach内部的视图)

例如,假设您只是将图像添加到HStack,则可以创建自定义的SwiftUI视图,例如:

struct MyImageView: View, Identifiable {
    // Conform to Identifiable:
    var id = UUID()
    // Name of the image:
    var imageName: String

    var body: some View {
        Image(imageName)
            .resizable()
            .frame(width: 50, height: 50)
    }
}

然后在您的HStack中:

// Images:
HStack(spacing: 10) {
    ForEach(images, id: \.self) { imageName in
        MyImageView(imageName: imageName)
    }
    Spacer()
}

答案 5 :(得分:0)

SwiftUI 2

您现在可以直接在@ViewBuilder块中使用控制流语句,这意味着以下代码是完全有效的:

struct ContentView: View {
    let elements: [Any] = [View1.self, View2.self]

    var body: some View {
        List {
            ForEach(0 ..< elements.count) { index in
                if let _ = elements[index] as? View1 {
                    View1()
                } else {
                    View2()
                }
            }
        }
    }
}

SwiftUI 1

除了FlowUI.SimpleUITesting.com的answer,您还可以使用@ViewBuilder并完全避免使用AnyView

@ViewBuilder
func buildView(types: [Any], index: Int) -> some View {
    switch types[index].self {
    case is View1.Type: View1()
    case is View2.Type: View2()
    default: EmptyView()
    }
}

答案 6 :(得分:0)

Swift 5

这似乎对我有用。

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

struct PageView: View {
    
   let elements: [Any] = [AMZ1(), AMZ2(), AMZ3()]
    
   var body: some View {
     TabView {
       ForEach(0..<elements.count) { index in
         if self.elements[index] is AMZ1 {
             AMZ1()
           } else if self.elements[index] is AMZ2 {
             AMZ2()
           } else {
             AMZ3()
      }
   }
}

答案 7 :(得分:-1)

我也想在同一列表中使用不同的视图,因此实现了高级列表视图,请参见here