SwiftUI中的内容是什么?

时间:2019-07-01 10:01:02

标签: swift swiftui

在文档中,我在不同的上下文中看到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用法?

3 个答案:

答案 0 :(得分:1)

重要的是要了解SwiftUI大量使用了泛型类型。在SwiftUI(和Combine)发布之前,我从未见过如此大量使用泛型的Swift代码。 SwiftUI中几乎所有符合View的类型(和符合ViewModifier的类型)都是通用类型。

ViewModifier

因此,首先让我们谈谈ViewModifierViewModifier是一个协议。其他类型可以符合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> } 的一些扩展定义了ViewModifierbody_makeView的默认设置,但我们可以忽略它们。

所以无论如何,我们可以看到_makeViewListContent的别名,_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>类型像ColorVStack<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有两个子视图:TT。 / 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)
}

由于viewBinderstruct的最后一个属性,因此可以将内容放在FormItem函数调用之后。

答案 2 :(得分:1)

我喜欢 Rob's answer,当我发现这个 SO 问题时,它回答了我的隐含问题,但我想我可以扩展 Kontiki 的评论,对于 Swift 或泛型的新手。 >


这个问题问了几件事,具体来说:

<块引用>

SwiftUI 中的内容是什么?

令人惊讶的是,SwiftUI 中没有实际的 Content 类、结构或类型(就我所见)!问题中的两个例子都证明了这一点。

我是什么意思? Content 是一个 generic,有点像“保存类型的变量”(尽管我觉得这个解释令人困惑)。

泛型真的很酷(它们是 Swift 和 XCode 自动完成知道您将字符串而不是整数放入数组中的原因),但在这种情况下,泛型 {{1 }} 仅用于表示符合 Content 协议的任意类型(例如,Viewalways Button,而不是 {{ 1}})。 名称 Button 完全是随意的——Apple 同样可以将其称为 TextContent,如果您正在编写托管自己内容的自定义类型,你可以选择任何你想要的名字。

如果您使用过更多依赖类和子类的语言(如 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 : ViewView 也只是某种视图(通过检查其他文档)。

查看 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)> 相比有两个重要区别:

  1. 我们必须始终返回相同类型的视图。
  2. 即使我们不知道返回的是什么视图类型,编译器也知道。