How can I increase maximum number of rows that are shown in a SwiftUI picker?

天涯浪子 提交于 2019-12-11 15:59:32

问题


I am trying to create a SwiftUI picker that users can use to select a number from 1000 to 20000 (in increments of 1000. for example 1000,2000,3000 .... ... 20000)

By default the SwiftUI picker can only hold 10 rows of text. How can I allow the SwiftUI picker to contain 20 rows of text?


回答1:


I guess you wrote something like this:

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
}

And then you tried to add a row for 11000 and got this error:

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")) {
                              ^~~~~~

The problem is that, due to limitations in the Swift language and in how SwiftUI is implemented, you can only have 10 subviews in a @ViewBuilder body.

Here are two ways to work around this.

One way, which is appropriate for your design, is to use 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
}

The other way, which would be more appropriate if your items didn't follow a simple pattern, is to group your items using 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 flattens the Group subviews into the Group's parent (in this case, into the Picker). Each Group can have up to 10 subviews, which can themselves be Groups, so by nesting Groups you can have arbitrarily many explicit elements in your Picker. But I recommend using ForEach.

If you want to understand where the 10 subview limit comes from, edit my second example, storing the Picker in a variable like this:

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

Now option-click the picker variable in Xcode to see its inferred type:

Let's reformat that more readably:

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

Wow, that's a big type! SwiftUI uses generic types like this heavily because it is more efficient at runtime. Because these are all struct types conforming to View, Swift stores this whole Picker and all its children in a single contiguous block of memory. That block can start out on the stack and only needs to be copied to the heap when SwiftUI eventually needs to type-erase it or store it long-term. Compare to UIKit, where every view is always separately allocated on the heap on creation.

ViewBuilder is the SwiftUI utility that assembles these complex views. Swift transforms the body of each Group into a call to ViewBuilder.buildBlock, with each view inside the Group body as a separate argument to ViewBuilder.buildBlock. Each of those arguments could be a separate type (e.g. a Group could have some Text children and some Image children). But Swift doesn't support variadic generics, so ViewBuilder has to define a version of buildBlock that takes a single view, and a version that takes two views, and a version that takes three views, and so on. It cannot define an infinite number of methods, because then the SwiftUI framework would be infinitely large. So it stops at 10 arguments:

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

That's why any view whose content is defined using ViewBuilder (which includes VStack, HStack, ZStack, Picker, List, Group, and others) can only have 10 direct subviews.



来源:https://stackoverflow.com/questions/57173197/how-can-i-increase-maximum-number-of-rows-that-are-shown-in-a-swiftui-picker

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!