39

In the documentation, I see Content in a different context:

/// 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
}

and here

/// 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 {

I can't find in the documentation the proper explanation of what does Content mean. Is there any predefined content usage in SwiftUI?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Vyacheslav
  • 26,359
  • 19
  • 112
  • 194
  • Content is just a way to indicate a generic type, which in the case of SwiftUI usually represents any kind of view. To learn more about swift generics, check: https://docs.swift.org/swift-book/LanguageGuide/Generics.html – kontiki Jul 01 '19 at 10:49

3 Answers3

67

It's important to understand that SwiftUI makes heavy use of generic types. Before the release of SwiftUI (and Combine), I had never seen any Swift code that makes such heavy use of generics. Almost all of the View-conforming types (and ViewModifier-conforming types) in SwiftUI are generic types.

ViewModifier

So, first let's talk about ViewModifier. ViewModifier is a protocol. Other types can conform to ViewModifier, but no variable or value can just have the plain type ViewModifier.

To make a type conform to ViewModifier, we define a body method that takes a Content (whatever that is) and returns a Body (whatever that is):

func body(content: Content) -> Body

A ViewModifier is essentially just this one method, that takes a Content as input and returns a Body as output.

What's Body? ViewModifier defines it as an associatedtype with a constraint:

associatedtype Body : View

This means we get to pick the specific type known as Body in our ViewModifier, and we can pick any type for Body as long as it conforms to the View protocol.

And what is Content? The documentation tells you it's a typealias, which means we probably don't get to pick what it is. But the documentation doesn't tell you what Content is an alias of, so we don't know anything about what body can do with the Content it receives!

The reason the documentation doesn't tell you is because Xcode is programmed not to show you a public symbol from the SDK if the symbol begins with an underscore (_). But you can see the true definition of ViewModifier, including the hidden symbols, by looking in the .swiftinterface file for SwiftUI. I explain how to find that file in this answer.

Consulting that file, we find the true definition of 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>
}

There are also some extensions to ViewModifier that define defaults for body, _makeView, and _makeViewList, but we can ignore those.

So anyway, we can see that Content is an alias for _ViewModifier_Content<Self>, which is a struct that doesn't define any interesting public interface, but does (in an extension) conform to View. So this tells us that, when we write our own ViewModifier, our body method will receive some sort of View (the specific type is defined by the framework and we can just call it Content), and return some sort of View (we get to pick the specific return type).

So here's an example ViewModifier that we can apply to any View. It pads the modified view and gives it a colored background:

struct MyModifier: ViewModifier {
    var color: Color

    func body(content: Content) -> some View {
        return content.padding().background(color)
    }
}

Note that we don't have to name the type of View returned by body. We can use some View and let Swift deduce the specific type.

We can use it like this:

Text("Hello").modifier(MyModifier(color: .red))

VStack

Now let's talk about VStack. The VStack type is a struct, not a protocol. It is generic, which means it takes type parameters (just like a function takes function parameters). VStack takes a single type parameter, named Content. This means VStack defines a family of types, one for every type it allows for Content.

Since VStack's Content parameter is constrained to conform to View, this means that for every View-conforming type, there is a corresponding VStack type. For Text (which conforms to View), there is VStack<Text>. For Image, there is VStack<Image>. For Color, there is VStack<Color>.

But we don't normally spell out the full type instance of VStack we're using, and we don't usually have the Content type be a primitive like Text or Image. The whole reason to use a VStack is to arrange multiple views in a column. The use of VStack tells Swift to arrange its subviews vertically, and the VStack's Content type parameter specifies the types of the subviews.

For example, when you write this:

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

you're actually creating an instance of this type:

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

The Content type parameter here is the type TupleView<(Text, Button<Text>)>, which is itself a generic type TupleView with its own type parameter named T, and T here is (Text, Button<Text>) (a 2-tuple, also called a pair). So the VStack part of the type tells SwiftUI to arrange the subviews vertically, and the TupleView<(Text, Button<Text>)> part tells SwiftUI that there are two subviews: a Text and a Button<Text>.

You can see how even this short example generates a type with several levels of nested generic parameters. So we definitely want to let the compiler figure out these types for us. This is why Apple added the some View syntax to Swift—so we can let the compiler figure out the exact type.

David
  • 3,285
  • 1
  • 37
  • 54
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Do you know how to extract the different tuple contents? How does apple do that for example with the SwiftUI Picker. They must somehow access the contents of the labels right? – krjw Oct 31 '19 at 17:04
  • That question is too involved to be just a comment on an answer. – rob mayoff Oct 31 '19 at 18:04
  • There are quite a lot of words and no answer to the original question: What is the semantics of "Content"? Say, in `TupleView`, "T" could be "Content", but it isn't. While in `ViewBuilder` in `buildBlock(_: _:)` "C0" stands for "Content0". And the is no clear demarcation line between "Content" and "Non-content". – artyom.stv Nov 04 '19 at 14:32
  • The original question does not use the word “semantics” at all. Also, there is no “the” semantics because `Content` means different things in different contexts. I explained what `Content` means in the two concrete examples given in the question. – rob mayoff Nov 04 '19 at 18:34
  • It would be great to add a final step where you declare `func padWithBackgroundColor` in a `extension View` so you can call `Text("Hello"). padWithBackgroundColor(.red)` directly. – Rivera Mar 31 '20 at 20:23
  • @Rivera If you're asking for help doing that, please post a new question. I don't think it's appropriate for this question. – rob mayoff Mar 31 '20 at 21:41
  • @robmayoff it was not a question, it was a suggestion to make your answer more complete. I could go ahead and edit it myself or post a different answer too. – Rivera Apr 02 '20 at 14:59
  • Post your own answer if you want. Please don't edit mine. – rob mayoff Apr 02 '20 at 15:32
14

This might also be helpful:

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()
        }
    }
}

Then use it this way:

FormItem(label: "Your Name") {
    TextField("name", text: $bindingToName)
}

Because the viewBinder is the last property of the struct, you can place the contents after the FormItem function call.

P. Ent
  • 1,654
  • 1
  • 12
  • 22
7

I love Rob's answer, which answered my implicit question when I found this SO question, but I thought I could expand on Kontiki's comment, for programmers new to Swift or generics.


This question asks a couple things, specifically:

What is Content in SwiftUI?

Surprisingly, there is no actual Content class or struct or type in SwiftUI (as far as I've seen)! Both examples in the question offer evidence of this.

What do I mean? Content is a generic, kinda like a "variable that holds a type" (though I find that explanation confusing).

Generics are really cool (they're the reason that Swift & XCode autocomplete know that you put, say, strings in an array and not integers), but in this case the generic Content is simply being used to represent an arbitrary type that conforms to the View protocol (as in, a Button and always a Button, not Text). The name Content is completely arbitrary—Apple could equally have called it Foo or MyView, and if you're writing custom types that host their own content, you can choose whatever name you want.

If you've used languages that rely more on classes and subclasses (like Java or C++ or basically every other big, typed language), then it's fair to say, for comparison, that this generic is being used to require all "content" to adhere to the "base class" View (to be clear: View is not a class in SwiftUI; it is a protocol and behaves differently). Except—for a given instance of a control (for example, a specific VStack), Content must always be the same type. Once a Button, always a Button. This is why sometimes you need to use AnyView.

All of this explained practically

How do I know all of this? The second example:

/// 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 {

This code declares the struct VStack, which is a generic type because it corresponds to an arbitrary struct/class/type that the type's author chose to call Content. Not any arbitrary type, though, because where Content : View limits callers to using types that implement the View protocol.

Rob's answer explains the other example—that Content for a ViewModifier is just some sort of View as well (by examining other documentation).

Looking at the initializer for VStack, you'll see it takes a function that returns Content:

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

When you create a VStack (or any other control with content), the content you provide is actually in that function—and from there, Swift can determine the concrete ("real") type of Content when it compiles:

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

As Rob explains above, the concrete type in this function is actually some sort of TupleView. But because VStack is generic (and also limited to implementors of View), all it knows is that you've provided some specific type that implements View—and whatever type that is, we call it Content.

Aside: some View

This also somewhat explains the usage of of some View in SwiftUI: though you could write out the full TupleView type every time you use it, if you added a Button at the end of your VStack, the function's type would change from TupleView<(Text, Text)> to TupleView<(Text, Text, Button)>, which is tedious to change everywhere it's needed. It's easier to say it's some View (as in, "this is a specific type that implements View and ignore everything else about it"). And using some View is safer than using View:

Returning some View has two important differences compared to just returning View:

  1. We must always return the same type of view.
  2. Even though we don’t know what view type is going back, the compiler does.

Aside: multiple returns in a closure?

If you look at our VStack example again:

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

You'll noticed that the function we use seems to have 2 return values (two Texts), using the implicit return syntax for Closures. But notably, this implicit return only works for functions with one expression (the feature is called Implicit Returns from Single-Expression Closures!). For example:

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

How can we return two things?

How Well Do You Know SwiftUI? describes it:

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 ( ) -> Content has a @ViewBuilder annotation. This is a new Swift 5.1 feature called Function Builders, which is what makes this syntax possible.

In short, the trailing closure used in VStack has the @ViewBuilder annotation, which is a function builder (Result Builder) that implicitly constructs TupleViews for your UI.

citelao
  • 4,898
  • 2
  • 22
  • 36
  • One thing I don't understand is for example in the `VStack` the argument `content: () -> Content` is a closure that returns a `Content` but when we initialize a `VStack` we don't return anything. In your example the `VStack`'s trailing closure just creates two `Text` views but does not return anything. How is that possible? – Mustafa Shujaie Sep 07 '21 at 18:02
  • 1
    @mfshujaie, if I understand you correctly, this is just a continuation of Swift's **implicit returns from single-expression closures** ([Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html)), although I don't know how those two `Text` views get implicitly converted to a `TupleView` (which is what I assume is happening). I imagine that's your question, though :D – citelao Sep 07 '21 at 19:58
  • Yes, exactly that's my question how do those two `Text` views are converted to a single `TupleView`? – Mustafa Shujaie Sep 08 '21 at 08:55
  • Found an answer here: https://blog.avenuecode.com/how-well-do-you-know-swiftui – Mustafa Shujaie Sep 08 '21 at 09:32
  • 1
    Oh, really cool! `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 ( ) -> Content has a @ViewBuilder annotation. This is a new Swift 5.1 feature called Function Builders, which is what makes this syntax possible`. I'll update my answer. – citelao Sep 08 '21 at 19:39
  • The concluding part of your answer wonderfully resolved the mystery of multiple returns in a closure. That was totally stumping me. – Soferio Jan 18 '23 at 08:13