如何增加 SwiftUI 选择器中显示的最大行数?
How can I increase maximum number of rows that are shown in a SwiftUI picker?
我正在尝试创建一个 SwiftUI 选择器,用户可以使用它来 select 从 1000 到 20000 的数字(以 1000 为增量。例如 1000,2000,3000 .... ... 20000 )
默认情况下,SwiftUI 选择器只能容纳 10 行文本。如何让 SwiftUI 选择器包含 20 行文本?
我猜你写的是这样的:
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
}
}
现在 option-click Xcode 中的 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 大量使用此类泛型类型,因为它在运行时效率更高。因为这些都是符合 View
的 struct
类型,所以 Swift 将整个 Picker
及其所有子项存储在一个连续的内存块中。该块可以从堆栈开始,仅当 SwiftUI 最终需要 type-erase 或将其存储 long-term 时才需要复制到堆中。与 UIKit 相比,其中每个视图在创建时总是在堆上单独分配。
ViewBuilder
是组装这些复杂视图的 SwiftUI 实用程序。 Swift 将每个 Group
的主体转换为对 ViewBuilder.buildBlock
的调用,Group
主体内的每个视图作为 ViewBuilder.buildBlock
的单独参数。这些参数中的每一个都可以是一个单独的类型(例如 Group
可以有一些 Text
个子级和一些 Image
个子级)。但是 Swift 不支持可变泛型,所以 ViewBuilder
必须定义一个采用单一视图的 buildBlock
版本,一个采用两个视图的版本,一个采用三个视图的版本观点等。它不能定义无限多的方法,因为那样 SwiftUI 框架将无限大。所以它在 10 个参数处停止:
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)>
这就是为什么任何内容使用 ViewBuilder
定义的视图(包括 VStack
、HStack
、ZStack
、Picker
、List
, Group
, and others) 只能有 10 个直接子视图。
我正在尝试创建一个 SwiftUI 选择器,用户可以使用它来 select 从 1000 到 20000 的数字(以 1000 为增量。例如 1000,2000,3000 .... ... 20000 )
默认情况下,SwiftUI 选择器只能容纳 10 行文本。如何让 SwiftUI 选择器包含 20 行文本?
我猜你写的是这样的:
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
}
}
现在 option-click Xcode 中的 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 大量使用此类泛型类型,因为它在运行时效率更高。因为这些都是符合 View
的 struct
类型,所以 Swift 将整个 Picker
及其所有子项存储在一个连续的内存块中。该块可以从堆栈开始,仅当 SwiftUI 最终需要 type-erase 或将其存储 long-term 时才需要复制到堆中。与 UIKit 相比,其中每个视图在创建时总是在堆上单独分配。
ViewBuilder
是组装这些复杂视图的 SwiftUI 实用程序。 Swift 将每个 Group
的主体转换为对 ViewBuilder.buildBlock
的调用,Group
主体内的每个视图作为 ViewBuilder.buildBlock
的单独参数。这些参数中的每一个都可以是一个单独的类型(例如 Group
可以有一些 Text
个子级和一些 Image
个子级)。但是 Swift 不支持可变泛型,所以 ViewBuilder
必须定义一个采用单一视图的 buildBlock
版本,一个采用两个视图的版本,一个采用三个视图的版本观点等。它不能定义无限多的方法,因为那样 SwiftUI 框架将无限大。所以它在 10 个参数处停止:
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)>
这就是为什么任何内容使用 ViewBuilder
定义的视图(包括 VStack
、HStack
、ZStack
、Picker
、List
, Group
, and others) 只能有 10 个直接子视图。