SwiftUI中的内容是什么?

Vya*_*lav 4 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
}
Run Code Online (Sandbox Code Playgroud)

和这里

/// 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 {
Run Code Online (Sandbox Code Playgroud)

我在文档中找不到有关其Content含义的正确解释 。contentSwiftUI中是否有任何预定义的用法?

rob*_*off 8

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

ViewModifier

因此,首先让我们谈谈ViewModifierViewModifier是一个协议。其他类型可以遵循ViewModifier,但没有任何变量或值只能具有普通类型ViewModifier

为了使类型符合ViewModifier,我们定义一个body方法,该方法接受Content(无论是什么)并返回Body(无论是什么):

func body(content: Content) -> Body
Run Code Online (Sandbox Code Playgroud)

A ViewModifier本质上就是这种方法,它以a Content作为输入并返回a Body作为输出。

什么BodyViewModifier将其定义为associatedtype具有约束的:

associatedtype Body : View
Run Code Online (Sandbox Code Playgroud)

这意味着我们来挑称为特定类型Body在我们的ViewModifier,我们可以选择任何类型的Body,只要它符合View协议。

有什么事Content吗?该文档告诉您它是一个typealias,这意味着我们可能不知道它是什么。但是文件并没有告诉你什么Content是虚拟的,所以我们不知道什么东西body可以用做Content它接收!

文档未告诉您的原因是,如果Xcode编程为如果符号以下划线(_)开头,则不会向您显示SDK中的公共符号。但是ViewModifier,通过在.swiftinterface文件中查找SwiftUI ,您可以看到的真实定义,包括隐藏的符号。我将在此答案中说明如何找到该文件。

查阅该文件,我们找到了的真实定义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>
}
Run Code Online (Sandbox Code Playgroud)

也有一些扩展,ViewModifier定义了默认值body_makeView_makeViewList,但我们可以忽略这些。

因此,无论如何,我们可以看到这Content是的别名_ViewModifier_Content<Self>,这是一个别名,它struct没有定义任何有趣的公共接口,但是(在扩展名中)符合View。所以这告诉我们,当我们编写自己ViewModifierbody方法时,我们的方法将收到某种View(特定类型由框架定义,我们可以调用它Content),并返回某种View(我们可以选择特定的返回类型) )。

因此,这是一个示例ViewModifier,我们可以将其应用于任何一个View。它填充修改后的视图并为其提供彩色背景:

struct MyModifier: ViewModifier {
    var color: Color

    func body(content: Content) -> some View {
        return content.padding().background(color)
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们不必命名由View返回的类型body。我们可以使用some ViewSwift来推导特定类型。

我们可以这样使用它:

Text("Hello").modifier(MyModifier(color: .red))
Run Code Online (Sandbox Code Playgroud)

VStack

现在让我们谈谈VStack。的VStack类型是一个struct,而不是一个协议。它是通用的,这意味着它接受类型参数(就像函数接受函数参数一样)。VStack采用一个名为的类型参数Content。这意味着VStack定义了一个家庭的类型,一种为每一种类型的它允许Content

由于VStackContent参数必须符合View,这意味着对于每个View符合类型,都有一个对应的VStack类型。对于Text(符合View),有VStack<Text>。因为Image,有VStack<Image>。因为Color,有VStack<Color>

但是我们通常不会拼出VStack我们正在使用的完整类型实例,并且通常不会将Content类型设为类似Textor 的原始类型Image。使用a的全部原因VStack是在一列中排列多个视图。使用的VStack告诉斯威夫特垂直排列其子视图,和VStackContent类型参数指定类型的子视图。

例如,当您编写此代码时:

VStack {
    Text("Hello")
    Button(action: {}) {
        Text("Tap Me!")
    }
}
Run Code Online (Sandbox Code Playgroud)

您实际上是在创建这种类型的实例:

VStack<TupleView<(Text, Button<Text>)>>
Run Code Online (Sandbox Code Playgroud)

Content这里的type参数是type TupleView<(Text, Button<Text>)>,它本身就是一个通用类型TupleView,其类型参数名为T,并且T(Text, Button<Text>)(2元组,也称为对)。因此VStack,该类型的TupleView<(Text, Button<Text>)>一部分告诉SwiftUI 垂直排列子视图,而该部分告诉SwiftUI有两个子视图:a Text和a Button<Text>

您甚至可以看到这个简短示例如何生成具有多个嵌套通用参数级别的类型。因此,我们绝对希望让编译器为我们找出这些类型。这就是Apple some View向Swift 添加语法的原因,因此我们可以让编译器找出确切的类型。


cit*_*lao 8

我喜欢Rob 的回答,当我发现这个 SO 问题时,它回答了隐含的问题,但我认为我可以为刚接触 Swift 或泛型的程序员扩展 Kontiki 的评论。

\n
\n

这个问题问了一些事情,具体来说:

\n
\n

SwiftUI 中的内容是什么?

\n
\n

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

\n

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

\n

泛型真的很酷(它们是 Swift 和 XCode 自动完成知道您将字符串而不是整数放入数组中的原因),但在这种情况下,泛型Content只是用于表示符合以下条件的任意类型:协议View(如 aButton始终为a Button, not Text)。该名称Content是完全任意的\xe2\x80\x94Apple 同样可以将其称为FooMyView,如果您正在编写托管其自己内容的自定义类型,则可以选择您想要的任何名称。

\n

如果您使用过更多依赖于类和子类的语言(例如 Java 或 C++ 或基本上所有其他大型类型化语言),那么可以公平地说,作为比较,此泛型用于要求所有“内容”遵循“基类” View(需要明确的View是:不是 SwiftUI 中的类;它是一个协议并且行为不同)。除了\xe2\x80\x94 之外,对于给定的控件实例(例如,特定的VStack),Content必须始终是相同的类型。一次Button永远Button。​ 这就是为什么有时您需要使用AnyView.

\n

所有这些都用实际行动解释了

\n

我怎么知道这一切?第二个例子:

\n
/// A view that arranges its children in a vertical line.\n@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)\npublic struct VStack<Content> where Content : View {\n
Run Code Online (Sandbox Code Playgroud)\n

这段代码声明了 struct VStack,它是一个泛型类型,因为它对应于该类型的作者选择调用的任意结构/类/类型Content。但不是任意类型,因为where Content : View限制调用者使用实现View协议的类型。

\n

Rob 的回答解释了另一个示例 xe2x80x94,Content对于 a 来说ViewModifier也只是某种视图(通过检查其他文档)。

\n

查看 VStack 的初始化程序,您会发现它需要一个返回的函数Content

\n
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)\n
Run Code Online (Sandbox Code Playgroud)\n

当您创建 VStack(或任何其他带有内容的控件)时,您提供的内容实际上位于该函数 \xe2\x80\x94 中,并且从那里,Swift 可以确定Content其编译时的具体(“真实”)类型:

\n
VStack { // Function starts at `{`\n    Text("test")\n    Text("test 2")\n} // Function ends at `}`\n
Run Code Online (Sandbox Code Playgroud)\n

正如 Rob 上面所解释的,这个函数中的具体类型实际上是某种TupleView. 但是因为 VStack 是通用的(并且也仅限于 的实现者View),所以它所知道的就是您提供了一些实现View\xe2\x80\x94 的特定类型,无论是什么类型,我们都将其称为Content

\n

在旁边:some View

\n

some View这也在一定程度上解释了SwiftUI 中的用法:虽然你可以在每次使用它时写出完整的TupleView类型,但如果你在 VStack 的末尾添加了一个 Button,函数的类型将从 变为TupleView<(Text, Text)>TupleView<(Text, Text, Button)>即在需要的地方进行更改很乏味。说起来更容易some View(例如,“这是一个实现 View 并忽略与它有关的所有其他内容的特定类型”)。并且使用some View比使用更安全View

\n
\n

返回some View与仅返回相比有两个重要的区别View

\n
    \n
  1. 我们必须始终返回相同类型的视图。
  2. \n
  3. 尽管我们不知道要返回什么视图类型,但编译器知道。
  4. \n
\n
\n

旁白:闭包中的多个返回?

\n

如果你再看一下我们的 VStack 示例:

\n
VStack { // Function starts at `{`\n    Text("test")\n    Text("test 2")\n} // Function ends at `}`\n
Run Code Online (Sandbox Code Playgroud)\n

您会注意到,我们使用的函数似乎有2 个返回值(两个Text),使用Closures的隐式返回语法。但值得注意的是,这种隐式返回仅适用于具有一个表达式的函数(该功能称为单表达式闭包的隐式返回!)。例如:

\n
VStack { // Function starts at `{`\n    Text("test")\n    Text("test 2")\n} // Function ends at `}`\n
Run Code Online (Sandbox Code Playgroud)\n

我们怎样才能返回两个东西呢?

\n

您对 SwiftUI 了解多少?描述它:

\n
\n

但在Trailing Closure内部,\xe2\x80\x99 仍然不可能以声明的方式将视图一个接一个地放置。如果您注意到上面的 HStack init 方法,最后一个参数是一个函数类型,( ) -> Content有一个@ViewBuilder注释。这是一个名为Function Builders的新 Swift 5.1 功能,它使这种语法成为可能。

\n
\n

简而言之,中使用的尾随闭包VStack具有@ViewBuilder注释,它是一个函数生成器(结果生成器),它为您的 UI 隐式构造TupleViews。

\n


P. *_*Ent 6

这也可能有帮助:

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后这样使用它:

FormItem(label: "Your Name") {
    TextField("name", text: $bindingToName)
}
Run Code Online (Sandbox Code Playgroud)

因为viewBinder是 的最后一个属性,所以struct您可以将内容放在FormItem函数调用之后。