What is Content in SwiftUI? - swift

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?

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.

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.

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:
We must always return the same type of view.
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.

Related

How can I know the source object of dot with no prefix, is it SwiftUI or is it Swift?

So, basic question probably but I would like to understand it clearly.
I don't understand the dot something syntax with no prefix. For example the navigationViewStyle below, I can guess that it is a method of the NavigationView which is a struct.
We didn't declare a Navigation View Struct because it's SwiftUI and that is style in this programmatic UI?
In that case when we write dot something how does it know which object to relate to? am I referencing the struct definition or a this instance of the struct?
If I write Self. or self. none of the methods show up.
Also for the .stack, I just picked .stack. When I checked the navigationViewStyle documentation, I did not find the three options (stack, automatic, columns) which appear when coding. What is the origin of the three options? Is it inherited from a higher class or a combintation of multiple protocols? In that case the logic scales up and down, how do you plan out what to do?
My problem is that when I read an already written code it seems organic but my issue is if I want to write code from scratch then unless I memorize the pattern I have no idea how to reach the same decision on what to write.
Also is this just for SwiftUI or in Swift in general?
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
SymbolGrid()
}
.navigationViewStyle(.stack)
}
}
}
To complete #hallo answer, when you have a method that have a parameter of some type, then you can use any static var that are define in the type without needing to specify the type.
struct MyType {
var myVar: Int
static var one = MyType(myVar:1)
static var two = MyType(myVar:2)
}
// suppose you have somewhere a func defined as :
func doSomething(with aVar: MyType {
// do something
…
}
// you can call the function :
doSomethin(with: MyType.one)
// or
doSomething(with: .one)
In the second call the compiler know that the type of data must be MyType so it will deduce that .one is MyType.one, so you do not have to specify MyType. Just use « .one ». This is some syntaxic sugar from Swift.
This also work for enum where you do not have to specify the type in such case.
.navigationViewStyle() takes anything conforming to NavigationViewStyle and the available styles are also defined as static computed variables for convenience. You can also write .navigationViewStyle(StackNavigationViewStyle()) for the same result, but that seems much less readable in my opinion and therefore you just write .navigationViewStyle(.stack)
You can always just ⌘ (command) + click on anything and then "Jump to Definition" to see (or ⌃⌘ + click) to see the underlying definition
#available(iOS 13.0, tvOS 13.0, watchOS 7.0, *)
#available(macOS, unavailable)
extension NavigationViewStyle where Self == StackNavigationViewStyle {
/// A navigation view style represented by a view stack that only shows a
/// single top view at a time.
public static var stack: StackNavigationViewStyle { get }
}
As you see NavigationViewStyle.stack will return a StackNavigationViewStyle

is it a good idea to pass an action as () -> Void to a component?

I'm new to swfitUI, and I'm building a component a bit like this:
// my solution
struct TodoItem {
var title: String
var action: (() -> Void)?
var body: some View {
HStack {
Text(title)
if let action = action {
Button(action: action, label: Image(image))
}
}
}
}
but my teammates not agree with this, they think I should not pass an action to a component, instead I should use ViewBuilder like this,
// my teammates' solution
struct TodoItem<Content>: View where Content: View {
var title: String
var content: Content
#inlinable public init(title: String, #ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
HStack {
Text(title)
content
}
}
}
they said it is more SwiftUI fashion, but I don't understand, in terms of usage, in my solution, anyone use this component only need to care about the title and the action, in my teammates', the user need to care about the title, the action, and how to build the button, it is clearly that my solution is more easy to use, and I didn't see any disadvantage in my solution.
Is my teammates' solution better than mine, why?
If you find yourself reaching for AnyView, you've left the happy-path of SwiftUI. There's a reason it's listed in the Infrequently Used Views section of the docs.
AnyView breaks a lot of SwiftUI's optimizations. It exists as an escape-hatch when you have no other choice.
Your code looks like all the examples I've seen from Apple so far. #ViewBuilder makes sense when your goal is be a container for a caller-generated View, and you want the implementation details to be decided by the caller. HStack is a good example. If the component should encapsulate the View implementation details, then it should generate the view itself using properties passed (which is what you're doing). So the question in this case is "is TodoItem a general tool that will be used in many different ways by many different callers?" If not, I'm not sure why you would pass a ViewBuilder.
Your update (removing AnyView) changes the question quite a bit. In that case it comes down to my last paragraph above: If TodoItem is intended to be a generic container that callers are expected to provide contents for, then the ViewBuilder is good. This assumes that TodoItem is about layout rather than about display, like HStack.
But if it's view that is about display, like Button or Text, then you should pass it properties and let it manage its internals. (Note that Button allows you to pass in a Label ViewBuilder, but it generally does not require it.)
A name like "TodoItem" sounds very much like the latter; it seems like a custom view that should manage its own appearance. But the main question is: how many callers pass different Views to this ViewBuilder? If all callers pass pretty much the same Views (or if there's only one caller), then it should be properties (like a Button). If there are many callers that pass different kinds of content Views, then it should use a ViewBuilder (like an HStack).
Neither is "more SwiftUI." They solve different problems within SwiftUI.
Your approach is correct, with only change (let will not work), so see below corrected:
struct TodoItem {
var title: String
var image: String // << this might also needed
var action: (() -> Void)?
var body: some View {
HStack {
Text(title)
if action != nil { // << here !!
Button(action: action!, label: { Image(image) })
}
}
}
}
Tested with Xcode 11.4 / iOS 13.4
About teammate's alternate: Storing View in member is not a "SwiftUI fashion"... so, fixing second variant I would store ViewBuilder into member and use it to inject View inside body. But anyway it's worse approach, because breaks integrity of UI component.

Having an VStack as a property in a Struct

I need to have a VStack as a property in a struct like this
struct Str
{
let view: VStack
....
}
The compiler says:
"Reference to generic type 'VStack' requires arguments in <...>
Insert '<<#Content: View#>>'
"
Xcodes "fix" produces:
struct Str
{
let view: VStack<Content: View>
...
}
But the compiler still complains:
"Expected '>' to complete generic argument list"
Is it allowed to have a VStack in a struct?
What you have discovered is that VStack is not really a type. We can more accurately describe it as a “type constructor”. It is declared like this:
struct VStack<Content> where Content : View {
...
}
You give it some other type as its Content argument, and it gives you a type. You give it the type Text and you get back the type VStack<Text>. You give it the type Image and you get back the type VStack<Image>. The types you get back are different types! A object of type VStack<Text> is not an object of type VStack<Image>.
So you just need to know what type to pass as the argument, right? Well, the problem is that lots of SwiftUI “types” are actually generic type constructors. So the type gets complex. Here's an example from an app I'm working on:
var body: some View {
VStack(spacing: 0) {
scene
if store.model.latestError != nil {
Divider()
ErrorView(store: errorStore)
.padding(20)
}
} //
.frame(width: 450)
}
What's the type of body? I can print it at runtime to find out:
print(type(of: AppView(store: store).body))
The output is
ModifiedContent<VStack<TupleView<(AnyView, Optional<TupleView<(Divider, ModifiedContent<ErrorView, _PaddingLayout>)>>)>>, _FrameLayout>
Notice that the type of the object returned by body exposes lots of details about body's implementation. For example, because the ErrorView has a padding modifier, the body object's type includes ModifiedContent<ErrorView, _PaddingLayout>. I can change the padding amount from 20 to 30 without changing the type. But if I remove the padding modifier entirely, it changes the type (by changing ModifiedContent<ErrorView, _PaddingLayout> to just ErrorView).
So how do you solve this? One way is to use AnyView:
struct Str {
let view: AnyView
...
}
let str = Str(view: AnyView(VStack {
...
}))
Note that sometimes you will hear advice that you should avoid AnyView. Here's Joe Groff explaining why:
You can use the AnyView wrapper to explicitly wrap up types where they can dynamically change.
However, the animation system will have an easier time if you can push this down the view graph as far as possible. You could have one Layout type that takes .portrait/.landscape, apply rotation, translation, etc. differently in response, and then the transition can animate

How can i add a custom View modifier for Shapes

I want to call my ImageView with some kind of modifier to make the Image in it round or square. The code should look like:
ImageView(withURL: newsItem.imageUrl).test(Circle())
To get this behaviour i read you can extend the view like i did below. It works perfectly fine with a String but i don't understand what's different with a Shape?
I get the error Message Protocol type 'Shape' cannot conform to 'Shape' because only concrete types can conform to protocols but i don't understand that either :/. Can someone explain me the Problem and what i can do about it?
struct ImageView: View {
var clipShape: Shape
var body: some View {
ZStack {
Image("swift")
.resizable()
.aspectRatio(contentMode: .fill)
.clipShape(self.clipShape)
.shadow(radius: 6)
}
}
}
extension ImageView {
func test(_ shape: Shape) -> Self {
var copy = self
copy.clipShape = shape
return copy
}
}
btw this Code is shortened to only show the Code for the specific Problem, the View contains more than that. Just so you don't question the use of it.
Thanks in adnvance!
EDIT:
For why shapes don't work and what to do look at: https://forums.swift.org/t/how-to-make-different-clipshape/27448/2
In my Case if found out simply clipShaping the whole ImageView does the job just fine.
I also wanna link this Creating custom modifiers for Swift UI views where you can read about custom modifiers
I found this question while looking for the same title, but different intention - I want to create a modifier that can be applies specifically to something conforming to Shape so that I can do Shape like effects with it - stroke, etc.
But while I haven't found my answer yet, I think the answer to yours is that ViewModifier doesn't work to insert/change internal values of a view - instead you should be considering a ViewBuilder.
There aren't many articles that I've found really highlighting how to and when to effectively choose to use ViewBuilder, but this one from Majid entitled The power of #ViewBuilder in SwiftUI does a pretty good job.
Apple's docs on ViewBuilder also (fortunately) include a bit of sample code in the reference (one of the few places for SwiftUI) that at least show you some basic ideas of how you can use it.
The basic gist is that ViewModifier always works in general with methods on View, where ViewBuilder is used as a means of composing views together, which sounds much more like what you want. In the end, they overlap significantly in what they can do - differing primarily in how they achieve it.
Its a 3 step process:
Make a ViewModifier
Extend View with a function to apply your modifier and return the modified view
Call your modifying function on an actual View
struct RedBackGroundModifier: ViewModifier {
func body(content: Content) -> some View {
content.background(Color.red)
}
}
extension View {
func makeTheBackGroundRed() -> some View {
self.modifier(RedBackGroundModifier())
}
}
struct ContentView: View {
var body: some View {
Text("Hello, World!").makeTheBackGroundRed()
}
}

Why SwiftUI's View Protocol use PAT?

View protocol is defined like this:
public protocol View : _View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
}
so View is now a PATed protocol, which can't be used as a return type directly, though swift 5.1's opaque return type can handle this, but why declare a associatedtype Body : View, not var body: View { get } directly?
Because if it is just a var body: Self.Body { get } - your entity, that implements View protocol, will not know the type of the body.
struct MyView: View {
var body: MyAnotherView {
//implementation...
}
}
This code will not compile and you would have to write this:
struct MyView: View {
var body: View {
//implementation...
}
}
And I think behind the scenes SwiftUI has to know the exact type of a View, not just a protocol
Prior to SwiftUI Swift doesn’t allow us to use protocols with associated types as return types but We can use “regular” protocols.
Compiler let you restrict by showing below error:
“Protocol can only be used as a generic constraint because it has Self or associated type requirements.”
What does it mean?
Compiler cannot infer the associated type from this definition and the return type would be incomplete.
Whenever we call the that function it always return different concrete type instead if same concrete type.
Compiler will not let you to perform the swapping, equal, compare operation on this concrete type. Even they adopt the same Protocol ( i.e it's PAT).
Because concret type may have different associated type that they fulfilled or use.
To avoid the different concrete type as return type at each call we use some keyword as opaque return type.
Opaque return type:
It's reverse of generic type.
It always return same concrete type.you and compiler know it.
If we use an opaque result type instead, we enforce that the function will always return the same concrete type.
Inside function while performing the operation we know the generic type.