【问题标题】:What is Content in SwiftUI?SwiftUI 中的内容是什么?
【发布时间】:2019-11-11 23:47:49
【问题描述】:

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

【问题讨论】:

标签: swift swiftui


【解决方案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

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

Content 是什么?文档告诉您它是typealias,这意味着我们可能无法选择它是什么。但是文档没有告诉你 Content 是什么别名,所以我们不知道 body 可以用它收到的 Content 做什么!

文档没有告诉您的原因是,如果符号以下划线 (_) 开头,Xcode 不会向您显示来自 SDK 的公共符号。但是您可以通过查看 SwiftUI 的 .swiftinterface 文件来查看 ViewModifier 的真实定义,包括隐藏符号。我解释了如何在this answer 中找到该文件。

查阅那个文件,我们找到了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&lt;Self&gt; 的别名,这是一个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

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

由于VStackContent 参数被约束为符合View,这意味着对于每个符合View 的类型,都有一个对应的VStack 类型。对于Text(符合View),有VStack&lt;Text&gt;。对于Image,有VStack&lt;Image&gt;。对于Color,有VStack&lt;Color&gt;

但是我们通常不会拼出我们正在使用的VStack 的完整类型实例,而且我们通常不会将Content 类型设为像TextImage 这样的原始类型。使用VStack 的全部原因是在一个列中排列多个视图。 VStack 的使用告诉 Swift 垂直排列其子视图,VStackContent 类型参数指定子视图的类型。

例如,当你这样写时:

VStack {
    Text("Hello")
    Button(action: {}) {
        Text("Tap Me!")
    }
}

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

VStack<TupleView<(Text, Button<Text>)>>

这里的Content类型参数是TupleView&lt;(Text, Button&lt;Text&gt;)&gt;类型,它本身是一个泛型类型TupleView,有自己的类型参数名为T,而T这里是(Text, Button&lt;Text&gt;)(一个2元组,也称为一对)。所以类型的VStack 部分告诉SwiftUI 垂直排列子视图,TupleView&lt;(Text, Button&lt;Text&gt;)&gt; 部分告诉SwiftUI 有两个子视图:TextButton&lt;Text&gt;

即使是这个简短的示例,您也可以看到如何生成具有多层嵌套泛型参数的类型。所以我们肯定想让编译器为我们找出这些类型。这就是 Apple 在 Swift 中添加 some View 语法的原因——这样我们就可以让编译器找出确切的类型。

【讨论】:

  • 你知道如何提取不同的元组内容吗?例如,Apple 如何使用 SwiftUI Picker 做到这一点。他们必须以某种方式访问​​标签的内容,对吗?
  • 这个问题太复杂了,不能只是对答案的评论。
  • 字数不少,原问题无解:“内容”的语义是什么?比如说,在TupleView&lt;T&gt; 中,“T”可能是“内容”,但不是。而在ViewBuilderbuildBlock&lt;C0, C1&gt;(_: _:) 中,“C0”代表“Content0”。 “内容”和“非内容”之间没有明确的界限。
  • 原始问题根本没有使用“语义”这个词。此外,没有“the”语义,因为Content 在不同的上下文中表示不同的事物。我在问题中给出的两个具体示例中解释了Content 的含义。
  • 最好添加最后一步,在extension View 中声明func padWithBackgroundColor,这样您就可以直接调用Text("Hello"). padWithBackgroundColor(.red)
【解决方案2】:

这也可能有帮助:

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函数调用之后。

【讨论】:

    【解决方案3】:

    我喜欢 Rob's answer,当我发现这个 SO 问题时,它回答了我的隐含问题,但我想我可以扩展 Kontiki 的评论,以供不熟悉 Swift 或泛型的程序员使用。


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

    SwiftUI 中的内容是什么?

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

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

    泛型真的很酷(它们是 Swift 和 XCode 自动完成功能知道您将字符串放入数组而不是整数的原因),但在这种情况下,泛型 Content只是用于表示符合View 协议的任意类型(如Button始终 Button,而不是Text)。 Content 的名称完全是任意的——Apple 也可以将其称为 FooMyView,如果您正在编写托管自己的内容的自定义类型,您可以选择任何您喜欢的名称想要。

    如果您使用的语言更多地依赖于类和子类(如 Java 或 C++ 或基本上所有其他大型类型化语言),那么公平地说,为了比较,这个泛型被用来要求所有“内容”以遵守“基类”View(要明确:View 不是 SwiftUI 中的类;它是一个协议并且行为不同)。除了——对于给定的控件实例(例如,特定的VStack),Content 必须始终是相同的类型。一旦Button总是Button。这就是为什么sometimes you need to use AnyView

    所有这些都得到了实际的解释

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

    /// 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,它是一个通用类型,因为它对应于该类型的作者选择调用@的任意结构体/类/类型 987654350@。但不是任何任意类型,因为where Content : View 限制调用者使用实现View 协议的类型。

    Rob 的回答解释了另一个例子——ViewModifierContent 也只是某种视图(通过检查其他文档)。

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

    @inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
    

    当你创建一个 VStack(或任何其他带有内容的控件)时,你提供的内容实际上是在那个函数中——并且从那里,Swift 可以在编译时确定Content 的具体(“真实”)类型:

    VStack { // Function starts at `{`
        Text("test")
        Text("test 2")
    } // Function ends at `}`
    

    正如 Rob 上面解释的那样,这个函数中的具体类型实际上是某种TupleView。但是因为 VStack 是通用(也仅限于View 的实现者),它所知道的只是您提供了一些特定类型 来实现View——并且不管是什么类型,我们都称它为Content

    旁白:some View

    这也解释了 SwiftUI 中 some View 的用法:虽然你可以在每次使用它时写出完整的 TupleView 类型,如果你在末尾添加了一个 Button您的 VStack,函数的类型将从 TupleView&lt;(Text, Text)&gt; 更改为 TupleView&lt;(Text, Text, Button)&gt;,这在需要的地方进行更改是很乏味的。说它是some View 更容易(例如,“这是一种实现 View 并忽略其他所有内容的特定类型”)。 And using some View is safer than using View:

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

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

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

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

    VStack { // Function starts at `{`
        Text("test")
        Text("test 2")
    } // Function ends at `}`
    

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

    func foo(n: Int) -> Int {
        n * 4
    }
    

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

    How Well Do You Know SwiftUI? 描述它:

    但是在 Trailing Closure 中,仍然不可能以声明的方式一个接一个地放置视图。如果您在上面的 HStack 初始化方法中注意到,作为 ( ) -&gt; Content 类型函数的最后一个参数有一个 @ViewBuilder 注释。这是一个名为 Function Builders 的 Swift 5.1 新特性,它使这种语法成为可能。

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

    【讨论】:

    • 我不明白的一件事是,例如在VStack 中,参数content: () -&gt; Content 是一个返回Content 的闭包,但是当我们初始化VStack 时,我们不会返回任何事物。在您的示例中,VStack 的尾随闭包仅创建两个 Text 视图,但不返回任何内容。这怎么可能?
    • @mfshujaie,如果我理解正确的话,这只是 Swift 单表达式闭包的隐式返回 (Closures) 的延续,虽然我不知道这两个Text 视图是如何被隐式转换为TupleView 的(我假设正在发生这种情况)。不过,我想这是你的问题:D
    • 是的,这正是我的问题,这两个 Text 视图如何转换为单个 TupleView
    • 哦,真酷! But inside a Trailing Closure, it still wouldn’t be possible to put views one after another in a declarative way. If you notice in the HStack init method above, the last parameter that is a function of type ( ) -&gt; Content has a @ViewBuilder annotation. This is a new Swift 5.1 feature called Function Builders, which is what makes this syntax possible。我会更新我的答案。
    猜你喜欢
    • 1970-01-01
    • 2020-10-22
    • 1970-01-01
    • 2019-12-21
    • 2014-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多