SwiftUI - Is this code instantiating a new Scene object? - ios

I'm a Java developer trying to learn Swift/SwiftUI. I am going through the Apple tutorial for SwiftUI (https://developer.apple.com/tutorials/swiftui/creating-and-combining-views) In it they have the following code snippet:
import SwiftUI
#main
struct LandmarksApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
I am trying to understand this portion:
var body: some Scene {
WindowGroup {
ContentView()
}
}
I understand that it creates a new variable called body of type Scene. I know that you can instantiate objects like this:
var x: String = "test"
but I'm not familiar with the syntax. Does that create a new object of type Scene?
I have gone through this
but I couldn't find this syntax on there.

This syntax is indeed hard nut to crack for Swift novices. It involves implicit getters, constructors, several kind of closure shortcuts, implicit return types, generic type inference and opaque types. Let's go step by step.
Getter syntax for computed properties
LandmarksApp conforms to the App protocol. This requires the definition of a body property of a type complying with the Scene protocol. Moreover, we are told in the documentation that the body is expected to be a read-only computed property.
The general syntax of a computed property's getter is:
var X : T {
get { ... }
...
}
But if a it's a read-only propery, a shortcut may be used: put the getter code in a block directly after the type and without the get:
var CX : T { ... } // that's a computed property with its getter
Moreover, you do not need a return if the getter code is made of a single expression: the return is implicit.
WindowGroup constructor using a closure
WindowsGroup is the type we are going to use. It complies with Scene. It is a generic type based on the type parameter Content that conforms to the View protocol, and we have a ContentView for that. So here the first try:
struct LandmarksApp: App {
var body : WindowGroup<ContentView> { // read-only getter
WindowGroup<ContentView>(content: myhelper) // return a newly constructed object
}
}
The constructor for WindowGroup<T> requires as argument a function that takes no argument and returns a T. To start simple, an ordinary function was used. It's defined as follows:
func myhelper() -> ContentView {
return ContentView()
}
Simplification based on closures and generics
We can simplify with a closure to replace the helper function:
var body : WindowGroup<ContentView> {
WindowGroup<ContentView>(content: { ()->ContentView in return ContentView() })
}
Swift can infer the types used in the closure from the context and it also allows an implicit return for closures made of a single expression. In absence of parameters, we remove in. All those simplification lead to:
var body : WindowGroup<ContentView> {
WindowGroup<ContentView>(content: { ContentView() })
}
Swift is also able to infer the generic type from the context. So this expression could be simplified to:
var body : WindowGroup<ContentView> {
WindowGroup (content: { ContentView() })
}
Last but not least, the most tricky part: Swift allows a special syntax if the closure is the last argument of a function. It's called trailing closures. This leads to:
var body : WindowGroup<ContentView> {
WindowGroup () { ContentView() }
}
This syntax also allows to get rid of the empty parenthesis:
var body : WindowGroup<ContentView> {
WindowGroup { ContentView() }
}
Opaque types
Until now, we used a body of a concrete well-known type. But couldn't the type of this variable be inferred? It would thus be tempting to write:
var body { // OUCH !!!
WindowGroup { ContentView() }
}
But Swift requires computed properties to have an explicit type. To keep it as general as possible but ensure compliance with the required protocol, an opaque type may be used:
var body : some Scene {
WindowGroup { ContentView() }
}
More on opaque types in this question.
Wrap up

Related

Cannot assign value of type 'V' to type some 'Protocol'

I have a protocol:
import SwiftUI
...
protocol MyProtocol : View
{
var aValue: CGFloat { get }
}
Then I have a property in a UIViewController:
var contentView: some MyProtocol = MyView()
Where MyView is:
struct MyView : MyProtocol
{
var aValue: CGFloat = 0.25
var body: some View
{
...
}
}
Back in my view controller I have:
func showView<V: MyProtocol>(view: V)
{
...
contentView = view // ERROR Happens here.
}
Cannot assign value of type 'V' to type 'some MyProtocol'.
Why do I get this error and how can it be avoided?
var contentView: some MyProtocol = MyView()
So the type of contentView is "some specific, secret (opaque) type, unknown to anything but the compiler, that conforms to MyProtocol, and also happens to be exactly MyView, even though nothing can know that." It's not "something that conforms to MyProtocol" which it seems maybe you're thinking it is. If you mean that, the syntax is:
var contentView: MyProtocol = MyView()
The point of some is that the type is statically known at compile-time by the compiler, but not known to the caller, or by anything else.
For example, even this would fail:
var contentView: some MyProtocol = MyView()
contentView = MyView() // Cannot assign value of type 'MyView' to type 'some MyProtocol'
The compiler will not prove that MyView is exactly the secret type that contentView used. (For most errors of this type I'd say the compiler "cannot prove," but in this case, it's an active decision to forbid proving the fact because that's what some does.)
That "secret" type is carried along, however, and is well defined, it's just opaque. For example, the following is fine:
var contentView: some MyProtocol = MyView()
let otherView = contentView // otherView is definitely the same as as contentView
contentView = otherView // so it can be assigned
At first pass, I expect the code you want is just the above var contentView: MyProtocol, but it's very possible you have a deeper misunderstanding about SwiftUI. You cannot just swap in arbitrary Views in SwiftUI. As a rule, everything should be decided at compile-time, not runtime. There are tools like AnyView to work around this, but generally should not be your first choice. I expect there's a deeper design problem here that isn't in your question.
For more details of opaque types, see SE-244.
See Rob's answer for a good explanation of why, currently, your view controller is generic as follows and you haven't realized it.
final class ViewController<View: MyProtocol> {
private(set) var contentView: View
init(contentView: View) {
self.contentView = contentView
}
func showView(view: View) {
contentView = view
}
}
The property initializer you're using only applies to one of the potentially infinite ViewControllers that may be.
extension ViewController where View == MyView {
convenience init() {
self.init(contentView: .init())
}
}

Swift closure syntax using { ... in }

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.

Swift UI use View in a Protocol (Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements)

I want to use View in a Protocol.
protocol Test {
var view: View { get }
}
Protocol 'View' can only be used as a generic constraint because it
has Self or associated type requirements
I just want to do the same thing as with my ViewController. Any idea?
protocol Test {
var viewController: UIViewController { get }
}
If I use an associated type, I get the error in my other protocols.
protocol Test2: Test { 
//STUB
}
Any idea how to solve this problem? Thanks :)
SwiftUI.View is a protocol, and because it uses Self (for example, in its body property), you cannot directly declare a property type as a View.
You can define an associated type on Test and constrain that type to be a View:
protocol Test {
associatedtype T: View
var view: T { get }
}
You can't directly use the protocol unless you declare it as associated type, but you can use the type erased AnyView instead:
protocol Test {
var view: AnyView { get }
}
Creating an AnyView instance might add some noise in the code, however it's easy to create.
Extending Cristik's solution:
protocol ViewFactoryProtocol {
func makeView(parameter: SomeType) -> AnyView
}

Change #State in a #ViewBuilder closure

I'm trying to understand how to change #State variables in #ViewBuilder closures. The following is just a simple example:
struct ContentView: View {
#State var width = CGFloat(0)
var body: some View { //Error 1
GeometryReader { geometry in //Error 2
self.width = geometry.size.width
return Text("Hello world!")
}
}
}
I'm getting some errors:
Error 1:
Function declares an opaque return type, but has no return statements
in its body from which to infer an underlying type
But the return is redundant because there's only one line of code inside the View computed property.
Error 2:
Cannot convert return expression of type 'GeometryReader<_>' to return
type 'some View'
Since I explicitly write return Text("...") shouldn't the type be clear?
What's the problem here?
First, you can't make arbitrary swift statements in a functionBuilder. There is a specific syntax allowed. In SwiftUI's case, it's a ViewBuilder, which under the hood is composing your type. When you make consecutive statements in SwiftUI, you are actually relying on the compiler to compose a new type underneath according to the rules of that DSL.
2nd, SwiftUI is a recipe, it's not an event system. You don't change state variables in the body function, you set up how things should react when state variables change externally. If you want another view to react in some way to that width, you need to define that content with respect to the width of the component you want. Without knowing what your ultimate goal is here, it's hard to answer how to relate the content to each other.
EDIT:
I was asked to elaborate on what exactly is allowed. Each functionBuilder has a different allowable syntax which is defined in the functionBuilder itself. This has a good overview on function builders in Swift 5.1: https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api
As for what SwiftUI is specifically looking for, it's essentially looking for each statement to return an instance of View.
// works fine!
VStack {
Text("Does this")
Text("Work?")
}
// doesn't work!
VStack {
Text("Updating work status...")
self.workStatus = .ok // this doesn't return an instance of `View`!
}
// roundabout, but ok...
VStack {
Text("Being clever")
gimmeView()
}
// fine to execute arbitrary code now, as long as we return a `View`
private func gimmeView() -> some View {
self.workingStatus = .roundabout
return Text("Yes, but it does work.")
}
This is why you got the obtuse error you got:
Cannot convert return expression of type 'GeometryReader<_>' to return type 'some View'
The type system can't construct any View out of View and essentially Void when you execute:
self.width = geometry.size.width
When you do consecutive View statements, underneath, it's still being converted into a new type of View:
// the result of this is TupleView<Text, Text>
Text("left")
Text("right")

Get only property defined in protocol causes compilation error when modifying inner property of object

Consider code like this:
protocol SomeProtocol {
var something: Bool { get set }
}
class SomeProtocolImplementation: SomeProtocol {
var something: Bool = false {
didSet {
print("something changed!")
}
}
}
protocol MyProtocol {
var myProperty: SomeProtocol { get }
}
class MyClass: MyProtocol {
var myProperty: SomeProtocol = SomeProtocolImplementation() {
didSet {
print("myProperty has changed")
}
}
}
var o: MyProtocol = MyClass()
o.myProperty.something = true
This code doesn't compile with error:
error: cannot assign to property: 'myProperty' is a get-only property
o.myProperty.something = true
~~~~~~~~~~~~ ^
Why? My property is of type of SomeProtocolImplementation, which is class type so it should be possible to modify it's inner property using reference to myProperty.
Going further, after modifying myProperty definition so that it looks like that:
var myProperty: SomeProtocol { get set }
something weird happens. Now the code compile (not a surprise), but the output is:
something changed!
myProperty has changed
So at this point SomeProtocolImplementation starts behaving like a value type - modyifing it's internal state causes that the "didSet" callback for myProperty is triggered. Just as SomeProtocolImplementation would be struct...
I actually find the solution, but I want also understand what's going on. The solution is to modify SomeProtocol definition to:
protocol SomeProtocol: class {
var something: Bool { get set }
}
It works fine, but I'm trying to understand why it behaves like this. Anybody able to explain?
First read what Class Only Protocol is. Concentrate on the note section that says:
Use a class-only protocol when the behavior defined by that protocol’s requirements assumes or requires that a conforming type has reference semantics rather than value semantics.
Above quote should get you the idea.
You are trying to get the behavior of reference type for your SomeProtocol's conforming class (i.e. SomeProtocolImplementation). You want to be able to change the value of something in future. So basically you are directing to the above quoted sentence.
If you need more clarification please consider the following more meaningful design where I changed the naming for convenience:
protocol Base: class {
var referenceTypeProperty: Bool { get set }
// By now you are assuming: this property should be modifiable from any reference.
// So, instantly make the protocol `Class-only`
}
class BaseImplementation: Base {
var referenceTypeProperty: Bool = false {
didSet {
print("referenceTypeProperty did set")
}
}
}
protocol Child {
var valueTypeProperty: Base { get }
// This property shouldn't be modifiable from anywhere.
// So, you don't need to declare the protocol as Class-only
}
class ChildImplementation: Child {
var valueTypeProperty: Base = BaseImplementation() {
didSet {
print("valueTypeProperty did set")
}
}
}
let object: Child = ChildImplementation()
object.valueTypeProperty.referenceTypeProperty = true
Any class that can provide behavior useful to other classes may declare a programmatic interface for vending that behavior anonymously. Any other class may choose to adopt the protocol and implement one or more of its methods, thereby making use of the behavior. The class that declares a protocol is expected to call the methods in the protocol if they are implemented by the protocol adopter.
Protocol Apple Documentation
When you try to 'set' value to a variable that is read-only - you are trying to change the protocol's implementation. Conforming classes can only consume information from protocol. In Swift we can write protocol extensions where we can have alternative methods for the protocol.
In short think of computed variables as functions. You are technically trying to change a function in this case.
I actually find the solution, but I want also understand what's going on.
I was just about to tell you to make SomeProtocol a class protocol, but you already figured that out. — So I'm a little confused as to what you don't understand.
You understand about reference types and value types, and you understand about class protocols and nonclass protocols.
Well, as long as SomeProtocol might be adopted by a struct (it's a nonclass protocol), then if you are typing something as a SomeProtocol, it is a value type. The runtime isn't going to switch on reference type behavior just because the adopter turns out to be a class instance; all the decisions must be made at compile time. And at compile time, all the compiler knows is that this thing is a SomeProtocol, whose adopter might be a struct.

Resources