如何增加 SwiftUI 选择器中显示的最大行数?
Posted
技术标签:
【中文标题】如何增加 SwiftUI 选择器中显示的最大行数?【英文标题】:How can I increase maximum number of rows that are shown in a SwiftUI picker? 【发布时间】:2019-07-23 22:57:33 【问题描述】:我正在尝试创建一个 SwiftUI 选择器,用户可以使用它来选择 1000 到 20000 之间的数字(以 1000 为增量。例如 1000,2000,3000 .... 20000)
默认情况下,SwiftUI 选择器只能容纳 10 行文本。如何让 SwiftUI 选择器包含 20 行文本?
【问题讨论】:
【参考方案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
s,因此通过嵌套Group
s,您可以在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
变量以查看其推断类型:
让我们重新格式化,使其更具可读性:
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 最终需要对它进行类型擦除或长期存储时才需要将其复制到堆中。与 UIKit 相比,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
等)只能有 10 个直接子视图。
【讨论】:
优秀的答案。工作完美。感谢您的帮助。 写得很好。有没有快速解释 10 限制的来源?在 Swift 中似乎不太可能有这样的限制(除非某处有 10 个硬编码 init 之类的可怕东西......) 我在回答中添加了解释。以上是关于如何增加 SwiftUI 选择器中显示的最大行数?的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI Picker 填充问题 - 选择器中的 ForEach 循环不填充