简体   繁体   English

如何使用 SwiftUI 获得动态的视图列表

[英]How to have a dynamic List of Views using SwiftUI

I can do a static List like我可以做一个静态列表,比如

List {
   View1()
   View2()
}

But how do i make a dynamic list of elements from an array?但是我如何从一个数组中创建一个动态的元素列表? I tried the following but got error: Closure containing control flow statement cannot be used with function builder 'ViewBuilder'我尝试了以下操作,但出现错误:包含控制流语句的闭包不能与函数构建器“ViewBuilder”一起使用

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

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

Is there any work around for this?有什么解决办法吗? What I am trying to accomplish is a List contaning dynamic set of elements that are not statically entered.我想要完成的是一个包含非静态输入的动态元素集的列表。

Looks like the answer was related to wrapping my view inside of AnyView看起来答案与将我的视图包装在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 View2.Type: return AnyView( View2() )
           default: return AnyView(EmptyView())
        }
    }
}

With this, i can now get view-data from a server and compose them.有了这个,我现在可以从服务器获取视图数据并组合它们。 Also, they are only instanced when needed.此外,它们仅在需要时才被实例化。

if/let flow control statement cannot be used in a @ViewBuilder block. if/let流控制语句不能在@ViewBuilder块中使用。

Flow control statements inside those special blocks are translated to structs.这些特殊块内的流控制语句被转换为结构。

eg例如

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

is translated to a ConditionalValue<View1, View2> .被转换为ConditionalValue<View1, View2>

Not all flow control statements are available inside those blocks, ie switch , but this may change in the future.并非所有流控制语句都在这些块中可用,即switch ,但这在未来可能会改变。

More about this in the function builder evolution proposal .函数构建器进化提案中更多地了解这一点。


In your specific example you can rewrite the code as follows:在您的具体示例中,您可以按如下方式重写代码:

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

You can use dynamic list of subviews, but you need to be careful with the types and the instantiation.您可以使用子视图的动态列表,但您需要注意类型和实例化。 For reference, this is a demo a dynamic 'hamburger' here, github/swiftui_hamburger .作为参考,这是一个动态的“汉堡包”演示, 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)

    }
}

I found a little easier way than the answers above.我找到了比上面的答案更简单的方法。

Create your custom view.创建您的自定义视图。

Make sure that your view is Identifiable确保您的视图是可识别的

(It tells SwiftUI it can distinguish between views inside the ForEach by looking at their id property) (它告诉 SwiftUI 它可以通过查看它们的 id 属性来区分 ForEach 内部的视图)

For example, lets say you are just adding images to a HStack, you could create a custom SwiftUI View like:例如,假设您只是将图像添加到 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)
    }
}

Then in your HStack:然后在你的 HStack 中:

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

You can do this by polymorphism:你可以通过多态来做到这一点:

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

SwiftUI 2 SwiftUI 2

You can now use control flow statements directly in @ViewBuilder blocks, which means the following code is perfectly valid:您现在可以直接在@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 SwiftUI 1

In addition to the accepted answer you can use @ViewBuilder and avoid AnyView completely:除了公认的答案,你可以使用@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()
    }
}

Is it possible to return different View s based on needs?是否可以根据需要返回不同的View

In short: Sort of简而言之:有点

As it's fully described in swift.org , It is IMPOSSIIBLE to have multiple Type s returning as opaque type正如在swift.org 中完整描述的那样,让多个Type返回为不透明类型不可能的

If a function with an opaque return type returns from multiple places, all of the possible return values must have the same type.如果具有不透明返回类型的函数从多个位置返回,则所有可能的返回值必须具有相同的类型。 For a generic function, that return type can use the function's generic type parameters, but it must still be a single type.对于泛型函数,该返回类型可以使用函数的泛型类型参数,但它仍然必须是单一类型。

So how List can do that when statically passed some different views?那么当静态传递一些不同的视图时, List如何做到这一点呢?

List is not returning different types, it returns EmptyView filled with some content view. List 不返回不同的类型,它返回填充了一些内容视图的EmptyView The builder is able to build a wrapper around any type of view you pass to it, but when you use more and more views, it's not even going to compile at all!构建器能够围绕您传递给它的任何类型的视图构建包装器,但是当您使用越来越多的视图时,它甚至根本不会编译! (try to pass more than 10 views for example and see what happens) (例如尝试传递 10 多个视图,看看会发生什么)

在此处输入图片说明

As you can see, List contents are some kind of ListCoreCellHost containing a subset of views that proves it's just a container of what it represents.如您所见, List内容是某种ListCoreCellHost其中包含一个视图子集,证明它只是它所代表内容的容器。

What if I have a lot of data, (like contacts) and want to fill a list for that?如果我有很多数据(如联系人)并想为此填写一个列表怎么办?

You can conform to Identifiable or use identified(by:) function as described here .您可以按照此处所述符合Identifiable或使用identified(by:)功能。

What if any contact could have a different view?如果任何联系人可以有不同的看法怎么办?

As you call them contact, it means they are same thing!当你称他们为接触时,这意味着他们是一回事! You should consider OOP to make them same and use inheritance advantages.您应该考虑 OOP 使它们相同并使用inheritance优势。 But unlike UIKit , the SwiftUI is based on structs.但与UIKit不同的是, SwiftUI是基于结构的。 They can not inherit each other.他们不能互相继承。

So what is the solution?那么解决方法是什么呢?

You MUST wrap all kind of views you want to display into the single View type.必须将要显示的所有类型的视图包装到单个View类型中。 The documentation for EmptyView is not enough to take advantage of that (for now). EmptyView的文档不足以利用它(目前)。 BUT!!!但!!! luckily, you can use UIKit幸运的是,你可以使用UIKit

How can I take advantage of UIKit for this我如何利用UIKit为此

  • Implement View1 and View2 on top of UIKit .UIKit之上实现View1View2
  • Define a ContainerView with of UIKit.用 UIKit 定义一个ContainerView
  • Implement the ContainerView the way that takes argument and represent View1 or View2 and size to fit.以接受参数的方式实现ContainerView并表示View1View2和适合的大小。
  • Conform to UIViewRepresentable and implement it's requirements.符合UIViewRepresentable并实现它的要求。
  • Make your SwiftUI List to show a list of ContainerView So now it's a single type that can represent multiple views让你的SwiftUI List显示一个ContainerView的列表,所以现在它是一个可以表示多个视图的单一类型

Swift 5斯威夫特 5

this seems to work for me.这似乎对我有用。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM