如何增加SwiftUI选择器中显示的最大行数?

时间:2019-07-23 22:57:33

标签: swiftui picker

我正在尝试创建一个SwiftUI选择器,用户可以使用它来从1000到20000(以1000为增量)中选择一个数字。例如1000,2000,3000 .... ... 20000)

默认情况下,SwiftUI选择器只能容纳10行文本。如何允许SwiftUI选择器包含20行文本?

1 个答案:

答案 0 :(得分:1)

我猜你写了这样的东西:

struct ContentView: View {
    var body: some View {
        Picker(selection: $value, label: Text("Pick One")) {
            Text("1000").tag(1000)
            Text("2000").tag(2000)
            Text("3000").tag(3000)
            Text("4000").tag(4000)
            Text("5000").tag(5000)
            Text("6000").tag(6000)
            Text("7000").tag(7000)
            Text("8000").tag(8000)
            Text("9000").tag(9000)
            Text("10000").tag(10000)
        }
    }

    @State var value: Int = 1000
}

然后您尝试为11000添加一行并出现此错误:

error: picker.xcplaygroundpage:5:31: error: cannot convert value of type 'Binding<Int>' to expected argument type 'Binding<_>'
            Picker(selection: $value, label: Text("Pick One")) {
                              ^~~~~~

问题在于,由于Swift语言的限制以及SwiftUI的实现方式,@ViewBuilder正文中只能有10个子视图。

有两种方法可以解决此问题。

一种适合您的设计的方法是使用ForEach

struct ContentView: View {
    var body: some View {
        Picker(selection: $value, label: Text("Pick One")) {
            ForEach(Array(stride(from: 1000, through: 20000, by: 1000))) { number in
                Text("\(number)").tag(number)
            }
        }
    }

    @State var value: Int = 1000
}

如果您的商品不遵循简单的模式,另一种方法更合适,那就是使用Group对商品进行分组:

struct ContentView: View {
    var body: some View {
        Picker(selection: $value, label: Text("Pick One")) {
            Group {
                Text("1000").tag(1000)
                Text("2000").tag(2000)
                Text("3000").tag(3000)
                Text("4000").tag(4000)
                Text("5000").tag(5000)
                Text("6000").tag(6000)
                Text("7000").tag(7000)
                Text("8000").tag(8000)
                Text("9000").tag(9000)
                Text("10000").tag(10000)
            }
            Group {
                Text("11000").tag(11000)
                Text("12000").tag(12000)
                Text("13000").tag(13000)
                Text("14000").tag(14000)
                Text("15000").tag(15000)
                Text("16000").tag(16000)
                Text("17000").tag(17000)
                Text("18000").tag(18000)
                Text("19000").tag(19000)
                Text("20000").tag(20000)
            }
        }
    }

    @State var value: Int = 1000
}

SwiftUI将Group子视图平铺到Group的父视图中(在这种情况下,平移到Picker中)。每个Group最多可以有10个子视图,这些子视图本身可以是Group,因此,通过嵌套Group,您可以在Picker中任意包含许多显式元素。但我建议使用ForEach

如果您想了解10次子视图限制的来源,请编辑第二个示例,将Picker存储在这样的变量中:

struct ContentView: View {
    var body: some View {
        let picker = Picker(selection: $value, label: Text("Pick One")) {
            Group {
            ...
            }
        }
        return picker
    }
}

现在,在Xcode中单击picker变量以查看其推断的类型:

inferred type of picker

我们重新格式化一下:

let picker: Picker<
    Text,
    Int,
    TupleView<(
        Group<TupleView<(
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View)>>,
        Group<TupleView<(
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View,
            some View)>>)>>

哇,真是个大人物! SwiftUI大量使用此类通用类型,因为它在运行时效率更高。由于这些都是符合struct的{​​{1}}类型,因此Swift将整个View及其所有子级都存储在单个连续的内存块中。该块可以从堆栈开始,只有在SwiftUI最终需要对其进行类型擦除或长期存储时,才需要将其复制到堆中。与UIKit相比,UIKit总是在创建时在堆上分别分配每个视图。

ViewBuilder是可汇编这些复杂视图的SwiftUI实用程序。 Swift将每个Picker的主体转换为对Group的调用,而ViewBuilder.buildBlock主体中的每个视图都作为Group的单独参数。这些参数中的每一个都可以是单独的类型(例如,ViewBuilder.buildBlock可以有一些Group子级和某些Text子级)。但是Swift不支持可变参数泛型,因此Image必须定义一个具有单个视图的ViewBuilder版本,一个具有两个视图的版本以及一个具有三个视图的版本,依此类推上。它不能定义无限数量的方法,因为SwiftUI框架将无限大。因此,它在10个参数处停止:

buildBlock

这就是为什么使用static func buildBlock() -> EmptyView Builds an empty view from a block containing no statements. static func buildBlock<Content>(Content) -> Content Passes a single view written as a child view through unmodified. static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)> static func buildBlock<C0, C1, C2>(C0, C1, C2) -> TupleView<(C0, C1, C2)> static func buildBlock<C0, C1, C2, C3>(C0, C1, C2, C3) -> TupleView<(C0, C1, C2, C3)> static func buildBlock<C0, C1, C2, C3, C4>(C0, C1, C2, C3, C4) -> TupleView<(C0, C1, C2, C3, C4)> static func buildBlock<C0, C1, C2, C3, C4, C5>(C0, C1, C2, C3, C4, C5) -> TupleView<(C0, C1, C2, C3, C4, C5)> static func buildBlock<C0, C1, C2, C3, C4, C5, C6>(C0, C1, C2, C3, C4, C5, C6) -> TupleView<(C0, C1, C2, C3, C4, C5, C6)> static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7>(C0, C1, C2, C3, C4, C5, C6, C7) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7)> static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8>(C0, C1, C2, C3, C4, C5, C6, C7, C8) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8)> static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> (包括ViewBuilderVStackHStackZStackPicker,和其他人,只能有10个直接子视图。