Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is Content in SwiftUI?

Tags:

swift

swiftui

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?

like image 573
Vyacheslav Avatar asked Jul 01 '19 10:07

Vyacheslav


People also ask

What is content view in SwiftUI?

ContentView is the main view. Its job is to define which views compose our app. In here, we have a single view, Text . If you run this in Xcode, this is what the app will look like: Notice the additional code after the ContentView struct: this is how we tell Xcode what to display in the preview panel on the right.

How do I create a content view in SwiftUI?

Choose File > New > File, select iOS as the platform, select the “SwiftUI View” template, and click Next. Name the new file MapView. swift and click Create.

What is a state in SwiftUI?

SwiftUI manages the storage of a property that you declare as state. When the value changes, SwiftUI updates the parts of the view hierarchy that depend on the value. Use state as the single source of truth for a given value stored in a view hierarchy.

What is SwiftUI data?

Data is a complex part of any app, but SwiftUI makes it easy to ensure a smooth, data-driven experience from prototyping to production. Discover @State and @Binding, two powerful tools that can preserve and seamlessly update your Source of Truth.


1 Answers

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.

like image 80
rob mayoff Avatar answered Oct 04 '22 11:10

rob mayoff