在文档中,我在不同的上下文中看到Content
:
/// A modifier that can be applied to a view or other view modifier,
/// producing a different version of the original value.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ViewModifier {
/// The content view type passed to `body()`.
typealias Content
}
在这里
/// A view that arranges its children in a vertical line.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct VStack<Content> where Content : View {
我在文档中找不到Content
含义的正确解释。 SwiftUI中是否有任何预定义的content
用法?
答案 0 :(得分:1)
重要的是要了解SwiftUI大量使用了泛型类型。在SwiftUI(和Combine)发布之前,我从未见过如此大量使用泛型的Swift代码。 SwiftUI中几乎所有符合View
的类型(和符合ViewModifier
的类型)都是通用类型。
ViewModifier
因此,首先让我们谈谈ViewModifier
。 ViewModifier
是一个协议。其他类型可以符合ViewModifier
,但是没有任何变量或值只能具有普通类型ViewModifier
。
要使类型符合ViewModifier
,我们定义一个body
方法,该方法采用Content
(无论是什么)并返回Body
(无论是什么) :
func body(content: Content) -> Body
ViewModifier
本质上就是这种方法,它以Content
作为输入并返回Body
作为输出。
Body
是什么? ViewModifier
将其定义为具有约束的associatedtype
:
associatedtype Body : View
这意味着我们可以在Body
中选择称为ViewModifier
的特定类型,并且我们可以为Body
选择任何类型,只要它符合{{1} }协议。
View
是什么?该文档告诉您它是Content
,这意味着我们可能不知道它是什么。但是文档没有告诉您typealias
是什么别名,因此我们对Content
所收到的body
可以做什么一无所知!
文档未告诉您的原因是,如果Xcode编程为如果符号以下划线(Content
开头,则不会向您显示公共符号。但是您可以看到_
的真实定义,包括隐藏的符号,但是可以在ViewModifier
文件中查找SwiftUI。我将说明如何在this answer中找到该文件。
咨询该文件,我们找到了.swiftinterface
的真实定义:
ViewModifier
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ViewModifier {
static func _makeView(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs, body: @escaping (SwiftUI._Graph, SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs) -> SwiftUI._ViewOutputs
static func _makeViewList(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs, body: @escaping (SwiftUI._Graph, SwiftUI._ViewListInputs) -> SwiftUI._ViewListOutputs) -> SwiftUI._ViewListOutputs
associatedtype Body : SwiftUI.View
func body(content: Self.Content) -> Self.Body
typealias Content = SwiftUI._ViewModifier_Content<Self>
}
的一些扩展定义了ViewModifier
,body
和_makeView
的默认设置,但我们可以忽略它们。
所以无论如何,我们可以看到_makeViewList
是Content
的别名,_ViewModifier_Content<Self>
并没有定义任何有趣的公共接口,但确实(在扩展名中)符合struct
。因此,这告诉我们,当我们编写自己的View
时,我们的ViewModifier
方法将收到某种body
(具体类型由框架定义,我们可以将其称为{ {1}}),然后返回某种类型的View
(我们可以选择特定的返回类型)。
因此,这里有一个示例Content
,我们可以将其应用于任何View
。它填充修改后的视图并为其提供彩色背景:
ViewModifier
请注意,我们不必命名View
返回的struct MyModifier: ViewModifier {
var color: Color
func body(content: Content) -> some View {
return content.padding().background(color)
}
}
的类型。我们可以使用View
并让Swift推断出特定的类型。
我们可以这样使用它:
body
some View
现在让我们谈谈Text("Hello").modifier(MyModifier(color: .red))
。 VStack
类型是VStack
,而不是协议。它是通用的,这意味着它接受类型参数(就像函数接受函数参数一样)。 VStack
采用一个名为struct
的单一类型参数。这意味着VStack
定义了一个家族类型,它为Content
所允许的每种类型都定义了一个类型。
由于VStack
的{{1}}参数被约束为符合Content
,这意味着对于每个符合VStack
的类型,都有一个对应的{{1} }类型。对于Content
(符合View
),有View
。对于VStack
,有Text
。对于View
,有VStack<Text>
。
但是我们通常不会拼写我们正在使用的Image
的完整类型实例,并且我们通常不让VStack<Image>
类型像Color
或VStack<Color>
。使用VStack
的全部原因是在一列中排列多个视图。 Content
的使用告诉Swift垂直排列其子视图,而Text
的{{1}}类型参数指定子视图的类型。
例如,当您编写此代码时:
Image
您实际上正在创建这种类型的实例:
VStack
这里的VStack
类型参数是类型VStack
,它本身是泛型类型Content
,其自身的类型参数名为VStack {
Text("Hello")
Button(action: {}) {
Text("Tap Me!")
}
}
和VStack<TupleView<(Text, Button<Text>)>>
这是Content
(一个2元组,也称为一对)。因此,该类型的TupleView<(Text, Button<Text>)>
部分告诉SwiftUI垂直排列子视图,而TupleView
部分告诉SwiftUI有两个子视图:T
和T
。 / p>
您甚至可以看到这个简短示例如何生成具有多个嵌套通用参数级别的类型。因此,我们绝对希望让编译器为我们找出这些类型。这就是为什么Apple在Swift中添加了(Text, Button<Text>)
语法的原因,因此我们可以让编译器找出确切的类型。
答案 1 :(得分:1)
这可能也有帮助:
private struct FormItem<Content:View>: View {
var label: String
let viewBuilder: () -> Content
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(label).font(.headline)
viewBuilder()
}
}
}
然后以这种方式使用它:
FormItem(label: "Your Name") {
TextField("name", text: $bindingToName)
}
由于viewBinder
是struct
的最后一个属性,因此可以将内容放在FormItem
函数调用之后。
答案 2 :(得分:1)
我喜欢 Rob's answer,当我发现这个 SO 问题时,它回答了我的隐含问题,但我想我可以扩展 Kontiki 的评论,对于 Swift 或泛型的新手。 >
这个问题问了几件事,具体来说:
<块引用>SwiftUI 中的内容是什么?
令人惊讶的是,SwiftUI 中没有实际的 Content
类、结构或类型(就我所见)!问题中的两个例子都证明了这一点。
我是什么意思? Content
是一个 generic,有点像“保存类型的变量”(尽管我觉得这个解释令人困惑)。
泛型真的很酷(它们是 Swift 和 XCode 自动完成知道您将字符串而不是整数放入数组中的原因),但在这种情况下,泛型 {{1 }} 仅用于表示符合 Content
协议的任意类型(例如,View
和 always Button
,而不是 {{ 1}})。 名称 Button
完全是随意的——Apple 同样可以将其称为 Text
或 Content
,如果您正在编写托管自己内容的自定义类型,你可以选择任何你想要的名字。
如果您使用过更多依赖类和子类的语言(如 Java 或 C++ 或基本上所有其他大型类型语言),那么可以公平地说,为了进行比较,这种泛型用于要求所有“内容”以遵守“基类”Foo
(需要明确的是:MyView
不是 SwiftUI 中的类;它是一种协议并且行为不同)。除了——对于给定的控件实例(例如,特定的 View
),View
必须始终是相同的类型。一次是 VStack
,总是 Content
。这就是为什么sometimes you need to use AnyView
。
我怎么知道这一切?第二个例子:
Button
此代码声明结构体 Button
,它是一个泛型类型,因为它对应于类型作者选择的任意结构体/类/类型调用 /// A view that arranges its children in a vertical line.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct VStack<Content> where Content : View {
。不过,不是任何任意类型,因为 VStack
限制调用者使用实现 Content
协议的类型。
Rob 的回答解释了另一个例子——where Content : View
的 View
也只是某种视图(通过检查其他文档)。
查看 VStack 的初始化程序,您会看到它接受一个返回 Content
的函数:
ViewModifier
当您创建 VStack(或任何其他带有内容的控件)时,您提供的内容实际上就在该函数中——从那里,Swift 可以确定 Content
的具体(“真实”)类型,当它编译:
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
正如 Rob 上面解释的那样,这个函数中的具体类型实际上是某种 Content
。但是因为 VStack 是通用(并且也仅限于 VStack { // Function starts at `{`
Text("test")
Text("test 2")
} // Function ends at `}`
的实现者),所以它只知道您已经提供了实现 {{1} 的某些特定类型 }}——不管是什么类型,我们称之为TupleView
。
View
这也稍微解释了 View
在 SwiftUI 中的用法:尽管您可以在每次使用它时写出完整的 Content
类型,如果您添加了一个 Button在 VStack 的末尾,函数的类型将从 some View
更改为 some View
,在需要的任何地方更改都很乏味。更容易说它是 TupleView
(例如,“这是一种实现 View 并忽略其他所有内容的特定类型”)。 And using some View
is safer than using View
:
返回 TupleView<(Text, Text)>
与仅返回 TupleView<(Text, Text, Button)>
相比有两个重要区别: