Having an VStack as a property in a Struct - swift

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

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

Syntax Meaning of a Class Name Followed by Curly Brackets in Swift [duplicate]

On the Apple SwiftUI Tutorial: Drawing Paths and Shapes, the following syntax is shown:
struct Badge: View {
var body: some View {
GeometryReader { geometry in
Path { path in
...
}
.fill(Color.black)
}
}
}
I did not see the syntax in the Swift docs on structs and feel very confused.
Additionally, how might one know that geometry is "in" the GeometryReader statement, whatever that means?
That has nothing to do with structs. That is closure syntax, where the variable name before in is the input argument to the closure.
In the context of SwiftUI, that closure is actually a ViewBuilder, which is a specific type of a function builder. You can read more about function readers and the other syntactic sugars that made SwiftUI possible here.
You can learn the type of the closure input argument(s) by option clicking on them in Xcode, just like any other variables. Or you can check the documentation of GeometryReader to learn what input arguments its closure accepts.
This is simplified syntax of trailing closure, so
Path { path in
...
}
is just the same as
Path( { (path) in
...
})
of used Path constructor
/// Initializes to an empty path then calls `callback` to add
/// the initial elements.
public init(_ callback: (inout Path) -> ())
By the way, this syntax is widely used in SwiftUI, so you will see the same in VStack { .. }, GeometryReader { g in ... }, ScrollView { ... } and almost all others UI elements of SwfitUI framework.

How to bind an array and List if the array is a member of ObservableObject?

I want to create MyViewModel which gets data from network and then updates the arrray of results. MyView should subscribe to the $model.results and show List filled with the results.
Unfortunately I get an error about "Type of expression is ambiguous without more context".
How to properly use ForEach for this case?
import SwiftUI
import Combine
class MyViewModel: ObservableObject {
#Published var results: [String] = []
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.results = ["Hello", "World", "!!!"]
}
}
}
struct MyView: View {
#ObservedObject var model: MyViewModel
var body: some View {
VStack {
List {
ForEach($model.results) { text in
Text(text)
// ^--- Type of expression is ambiguous without more context
}
}
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView(model: MyViewModel())
}
}
P.S. If I replace the model with #State var results: [String] all works fine, but I need have separate class MyViewModel: ObservableObject for my purposes
The fix
Change your ForEach block to
ForEach(model.results, id: \.self) { text in
Text(text)
}
Explanation
SwiftUI's error messages aren't doing you any favors here. The real error message (which you will see if you change Text(text) to Text(text as String) and remove the $ before model.results), is "Generic parameter 'ID' could not be inferred".
In other words, to use ForEach, the elements that you are iterating over need to be uniquely identified in one of two ways.
If the element is a struct or class, you can make it conform to the Identifiable protocol by adding a property var id: Hashable. You don't need the id parameter in this case.
The other option is to specifically tell ForEach what to use as a unique identifier using the id parameter. Update: It is up to you to guarentee that your collection does not have duplicate elements. If two elements have the same ID, any change made to one view (like an offset) will happen to both views.
In this case, we chose option 2 and told ForEach to use the String element itself as the identifier (\.self). We can do this since String conforms to the Hashable protocol.
What about the $?
Most views in SwiftUI only take your app's state and lay out their appearance based on it. In this example, the Text views simply take the information stored in the model and display it. But some views need to be able to reach back and modify your app's state in response to the user:
A Toggle needs to update a Bool value in response to a switch
A Slider needs to update a Double value in response to a slide
A TextField needs to update a String value in response to typing
The way we identify that there should be this two-way communication between app state and a view is by using a Binding<SomeType>. So a Toggle requires you to pass it a Binding<Bool>, a Slider requires a Binding<Double>, and a TextField requires a Binding<String>.
This is where the #State property wrapper (or #Published inside of an #ObservedObject) come in. That property wrapper "wraps" the value it contains in a Binding (along with some other stuff to guarantee SwiftUI knows to update the views when the value changes). If we need to get the value, we can simply refer to myVariable, but if we need the binding, we can use the shorthand $myVariable.
So, in this case, your original code contained ForEach($model.results). In other words, you were telling the compiler, "Iterate over this Binding<[String]>", but Binding is not a collection you can iterate over. Removing the $ says, "Iterate over this [String]," and Array is a collection you can iterate over.

What is Content in 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?
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.

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.