重要的是要了解SwiftUI大量使用泛型类型。在SwiftUI(和Combine)发布之前,我从未见过任何使用泛型如此频繁的Swift代码。几乎所有符合View和ViewModifier的类型在SwiftUI中都是泛型类型。
ViewModifier
首先让我们谈谈ViewModifier。ViewModifier是一个协议。其他类型可以符合ViewModifier,但是没有变量或值可以只具有纯ViewModifier类型。
为了使类型符合ViewModifier,我们定义一个body方法,该方法接受Content(无论它是什么)并返回Body(无论它是什么):
func body(content: Content) -> Body
ViewModifier
本质上只是一个方法,它以Content
作为输入,并返回Body
作为输出。
什么是Body
?ViewModifier
将其定义为带有约束的associatedtype
:
associatedtype Body : View
这意味着我们可以在
ViewModifier
中选择具体类型,即
Body
,只要它符合
View
协议,我们就可以选择任何类型作为
Body
。
那么
Content
是什么呢?文档告诉你它是一个
typealias
,这意味着我们可能不能选择它的内容。但是文档没有告诉你
Content
是什么别名,所以我们不知道
body
可以用接收到的
Content
做什么!
文档没有告诉你的原因是,如果符号以下划线(
_
)开头,Xcode程序会编程不显示SDK中的公共符号。但是,您可以通过查看SwiftUI的
.swiftinterface
文件来查看
ViewModifier
的真实定义,包括隐藏的符号。我在
这个答案中解释了如何找到该文件。
查阅该文件,我们找到了
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
方法将接收某种类型的View
(具体类型由框架定义,我们只需称之为Content
),并返回某种类型的View
(我们可以选择特定的返回类型)。
下面是一个示例ViewModifier
,我们可以将其应用于任何View
。它给修改后的视图添加填充并给其设置彩色背景:
struct MyModifier: ViewModifier {
var color: Color
func body(content: Content) -> some View {
return content.padding().background(color)
}
}
请注意,我们不必命名由
body
返回的
View
类型。我们可以使用
some View
并让Swift推断具体类型。
我们可以像这样使用它:
Text("Hello").modifier(MyModifier(color: .red))
VStack
现在让我们来谈谈VStack
。 VStack
类型是一个struct
,而不是协议。它是泛型的,这意味着它接受类型参数(就像函数接受函数参数一样)。VStack
接受一个单一的类型参数,命名为Content
。这意味着VStack
定义了一组类型,每个类型都允许用于Content
。
由于VStack
的Content
参数被限制为符合View
,这意味着对于每个符合View
的类型,都有一个相应的VStack
类型。对于Text
(符合View
),存在VStack<Text>
。对于Image
,存在VStack<Image>
。对于Color
,存在VStack<Color>
。
但是,我们通常不会拼写出正在使用的VStack
的完整类型实例,也不会将Content
类型作为原始类型,如Text
或Image
。使用VStack
的整个原因是在列中排列多个视图。使用VStack
告诉Swift在垂直方向上排列其子视图,而VStack
的Content
类型参数指定子视图的类型。
例如,当您编写以下内容时:
VStack {
Text("Hello")
Button(action: {}) {
Text("Tap Me!")
}
}
实际上,您正在创建此类型的一个实例:
VStack<TupleView<(Text, Button<Text>)>>
这里的
Content
类型参数是
TupleView<(Text, Button<Text>)>
类型,它本身是一个带有自己类型参数
T
的通用类型
TupleView
,而
T
在这里是
(Text, Button<Text>)
(一个2元组,也称为一对)。因此,类型中的
VStack
部分告诉SwiftUI垂直排列子视图,而
TupleView<(Text, Button<Text>)>
部分告诉SwiftUI有两个子视图:一个
Text
和一个
Button<Text>
。
您可以看到,即使是这个简短的示例也会生成具有多层嵌套通用参数的类型。因此,我们肯定希望让编译器为我们解决这些类型。这就是为什么苹果添加了
some View
语法到Swift中 - 这样我们就可以让编译器找出确切的类型。