Learning Swift – Day 202

Daniel Steinberg Workshop: Bringing SwiftUI to your app

Live from Pragma Conference, Bologna, 9 October 2019

Registration done, now waiting for the workshop to start.

Daniel Steinberg just got on stage! Here are some of the key aspects of today!

  • In Swift 5 if you have a function that has only one statement, we can omit the return keyword.
  • VStack have a function builder component where the last parameter accepts a closure. Spacer() pushes views as far as possible from each other.
  • Buttons do not talk to Text (Labels) anymore. Labels do not have names anymore… We need to create a property for the text inside of the label, then, inside the body property, we need to pass in a Text(propertyName) and then, in the last parameter of the Button initialiser, write self.message = "What you want to change goes here". But that will not compile unless we write @State in front of the property that is controlling the text content outside… These are called property wrappers, new to Swift 5.1.
  • SwiftUI wants to make sure that anything you knew in the past doesn’t work anymore! Now buttons doesn’t have isEnabled anymore as a property, but a .disabled(\_: Bool) method that requires another property to be passed inside. Of course this property will need another @State property…
  • We can replace this call to Text() with a separate file which has an uninitialised constant called message which will then be called inside ContentView.
  • If we move also the Button to its own file we need it to have a @Binding property to message, which will be called inside ContentView with $message. The $ sign is the binding sign.

Happily enough, I found that the Documentation for this action is updated and it writes the following:

Summary

A manager for a value that provides a way to mutate it.

Declaration

Discussion

Use a binding to create a two-way connection between a view and its underlying model. For example, you can create a binding between a Toggle and a Bool property of a State. Interacting with the toggle control changes the value of the Bool, and mutating the value of the Bool causes the toggle to update its presented state.

You can get a binding from a State by accessing its binding property. You can also use the $ prefix operator with any property of a State to create a binding.

Another quite new thing is that we now create our model as classes and our views as structs. The exact contrary of what we used to do.

There are many “manager”: we saw Binding before, then we get @Published, which is coming from the Combine framework and is so defined in the Documentation:

Generic Structure

Published

A type that publishes a property marked with an attribute.

Declaration

Overview

Publishing a property with the @Published attribute creates a publisher of this type. You access the publisher with the $ operator, as shown here:

Important

The @Published attribute is class-constrained. Use it with properties of classes, not with non-class types like structures.

It seems that we have to use this with an ObservableObject class whenever possible, even if it is not stated in that doc. Let’s try this one:

Protocol

ObservableObject

A type of object with a publisher that emits before the object has changed.

Declaration

Overview

By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its @Published properties changes.

All this seems more advanced than what we looked at today, and very interesting. I think it is just natural that I am feeling confused right now, but I can’t wait to see what we are going to do this afternoon.

Please now try to read this and ping me if you understand what it means!

Generic Structure

EnvironmentObject

A dynamic view property that uses a bindable object supplied by an ancestor view to invalidate the current view whenever the bindable object changes.

Declaration

Overview

You must set a model object on an ancestor view by calling its environmentObject(_:) method.

In the last of this morning’s apps we used this method but only when we had to set the Previews, which in Mojave are totally irrelevant.

The last one we should look at before continuing the workshop is this one:

Generic Structure

State

A persistent value of a given type, through which a view reads and monitors the value.

Declaration

Overview

SwiftUI manages the storage of any property you declare as a state. When the state value changes, the view invalidates its appearance and recomputes the body. Use the state as the single source of truth for a given view.

A State instance isn’t the value itself; it’s a means of reading and mutating the value. To access a state’s underlying value, use its value property.

Only access a state property from inside the view’s body (or from functions called by it). For this reason, you should declare your state properties as private, to prevent clients of your view from accessing it.

You can get a binding from a state with the binding property, or by using the $ prefix operator.


So, here we start with the afternoon half of the workshop.

State is being stored locally and is used to track the changes of a value. If we have other views that require to know about that value. If it just need to know about it, it can be just a let, otherwise, if it changes it, we need to use Binding, which means that I need a 2-way connection. To access a value a in another view we need to use the $ sign.

If we have a class a: A and we need to see its changes elsewhere, we need to label it as ObservableObject. Every property of a must be marked as @Published. Who watches a to see what has changed? A content view is going to watch for changes, and also other views are going to do so. They need to register for a but they need to be marked as @ObservedObjects. We create an instance of a then we create an instance of the extra views that need the dependency injection and pass the properties to their initialisers.

If we have an ObservableObject class a, with @Published properties but we have a content view with a couple of tab views, we create two instances of the views we need to work on and, inside of our SceneDelegate file, we add an instance of a passed to the environmentObject method of the contentView property. At this point, we pull out the object from there using the @EnvironmentObject manager.

Forgive me if this doesn’t sound too clear but this is what I understood from the recap after the launch-break! I promise I will go over this again and again in the future. I underline that this is my lack of experience that makes me slow and fall behind. Daniel’s talk (and drawings!!!) were so good! He was using a nice app on the iPad that I think was called Penultimate to show us the connections between different UI elements and their wrappers.

The next app we built was evolving from a project Daniel gave us… being an already working project that needed to be improved we needed to get a quick grasp on what was already there, thing that, unfortunately, I struggled a lot to do. All this is so different from UIKit (which is the only thing I knew before today) that I found the syntax quite chaotic. I know, closures and key paths are one of the most powerful features of Swift but I still need some good studying time to get comfortable with them.

The live coding was well done by Daniel (and thank you!, slow enough to be possible to follow it!) but after a while I was too tired to be able to follow, maybe also because of the very generous lunch!

I am really grateful to Mr. Steinberg for giving us the project files and the slides to allow us to go through them again and again and review things. Please do not forget to check out his website and his latest book (cannot find the link, use this for all his published books)!

100 Days of SwiftUI — Day 16

Project 1, part one

This first project aims to build a check-sharing app to calculate how much each one should pay and how much tip should be given. The name of the project is WeSplit. We are going to learn the basics of UI design, how to let users enter values and select from options, and how to track program state.

Introduction

Create a new SingleView App with interface based on SwiftUI, call it “WeSplit” and save it somewhere sensible. Make sure that you are not selecting the bottom three checkboxes and, optionally, check or leave unchecked the “Create a Git repository on this Mac” checkbox — I hit it.

Understanding the basic structure of a SwiftUI app

We start this section with an overview of the files in the project that we just created. The AppDelegate.swift file is very similar to what was before in UIKit and it contains code used to manage our app. In the past we used to write code in here to manage windows, view controllers and so on, but nowadays it is less common to do so. If you look at it and compare it with one from a UIKit app it will be much trimmed down. There is a lot of UIScene calls in there, which bring us to the next file.

The SceneDelegate.swift is the real new thing for SwiftUI as it contains the code that will launch one window in our app. So, all the viewWillAppear and its brothers are now here in the form of sceneWillEnterForeground and similar. The most important method here is scene(_:willConnectTo:optios:) which creates an instance of ContentView(), downcasts the scene as a UIWindowScene, creates a UIWindow with it, sets its rootViewController to be a UIHostingController with the content view as the root view, sets the window of the SceneDelegateclass to be the just declared UIWindow, finally making it key and visible.

The ContentView.swift replaces what the ViewController.swift file used to be and where we will be doing most of the work for this project. After the import SwiftUI statement, which imports the new framework (instead of UIKit). Then we find the struct ContentView: View declaration, which is a struct conforming to the View protocol. This is the basic protocol that must be adopted by anything we want to draw on the screen.

Protocol

View

A type that represents a SwiftUI view.

Declaration

Overview

You create custom views by declaring types that conform to the View protocol. Implement the required body computed property to provide the content and behavior for your custom view.

This is quite disappointing, right? Better to get used to it, though, this is SwiftUI by now. The new thing here is that computed property, body, which returns some View, an opaque return type introduced in Swift 5. That some means that this property must return something that conforms to the View protocol but it must always be the same kind of view (we cannot sometimes return one type of thing and other times a different one). I wonder then why it is called some, if it is not some but precisely the same kind of thing!!! Paul promises further explanation in the future, so I will trust him (never being let down on that!).

The line Text("Hello World") is what UILabel were before. I am not sure I like that, I find it somehow confusing but it may just be the new thing. The part below is something about previews, a feature available in Catalina, which I don’t have (and by now don’t want), so I will leave this out.

Creating a form

To create a place for user to input text we need to use a dedicated view type called Form, a scrolling list of static controls, capable though of accepting interactive controls (text fields, switches, etc…). Simply wrap the Text instance inside a Form { } one. Here is the Documentation for Form:

Generic Structure

Form

A container for grouping controls used for data entry, such as in settings or inspectors.

Declaration

Overview

SwiftUI renders forms in a manner appropriate for the platform. For example, on iOS, forms appear as grouped lists. Use Section to group different parts of a form’s content.

And here is the link to Section: an affordance for creating hierarchical view content. It is declared like this: struct Section<Parent, Content, Footer>. Looks like grouped table views, maybe?

If we want more than 10 items in our Form, we need to wrap them into Groups. Interestingly enough, Groups don’t change the way the user interface looks, they are just workarounds for SwiftUI limitations by now. If we want a real difference in the UI we should use Section.

Adding a navigation bar

A great addition of SwiftUI is that it doesn’t allow anymore to place view by default outside of the Safe Area. The form we used, though, if scrolled, still goes outside of it. What we used to do in UIKit was to embed the view controller inside of a navigation controller. This, in SwiftUI is done through wrapping our views inside of a NavigationView element. Right now SwiftUI is very slow in the auto-completion code but very fast at showing you incomprehensible errors before you can do anything about it. Right now the app shows a grey area about the Form but if we scroll up a white space appears and out Form disappears behind it.

We normally want to put some sort of title in the navigation bar, and we can do that by attaching a modifier to that navigation bar so that all what we have placed inside will be affected.. Modifiers are regular methods with just one difference: they always return a new instance of what we used them on. This is where SwiftUI can quickly become a lot of code-noise, but we will stick with it by now! After the closing braces of the Form, add .navigationBarTitle(Text("SwiftUI")). This places a “label” inside the bar title, but it is also possible to pass it a StringProtocol object, that is, a simple String. The result looks the same, so I cannot say what is better by now. The one we use is the only one which is actually fully documented.

Summary

Configures the title in the navigation bar for this view.

Declaration

Discussion

This modifier only takes effect when this view is inside of and visible within a NavigationView.

Parameters

title

A description of this view to display in the navigation bar.

Paul says that the other version is just a shortcut of this one. If we want to have a smaller font (what was largeTitle = .never before), we need to add , displayMode: .inline). The options here are .automatic, .inline and .none.

Modifying program state

“Our views are a function of their state”, is a motto for SwiftUI developers. The active collection of settings that describe how things are in this moment is called state! If we quit an app, its state will be saved and when we launch it again, its state will be loaded back. While we are using it, all the integers, Booleans, strings etc… will be called state and stored in RAM memory.

In SwiftUI what appears on the screen is determined by the state of our program. In UIKit the UI was driven by events called messages between every element of the app. This programming approach makes it very hard for an app to store its state so even very famous apps do not save the user’s state inside themselves.

Now remove all what we added previously inside the body property and replace it with a Button with a text of “Tap Count: \(tapCount)”, and a running closure of self.tapCount += 1. Of course add a var tapCount = 0 as a property at the top of the struct.

This won’t compile as the left size of the modifier is immutable. We should add the mutating keyword to the closure but this is not allowed for computer properties.

The solution is a property wrapper, an attribute to be placed before properties to enhance their possibilities. In this case, we need to add @State before the var tapCount = 0 line. This modifier allows a value to be stored separately by SwiftUI in a place where it can indeed be modified.

As a wrap-up to this section, we get told that @State is designed for simple properties that are stored in one view. Apple suggests that we add private in front of these properties.

Binding state to user interface controls

The @State property wrapper lets us do what mutating used to, so that we update our project according to how our properties change.

To go back to the original intent of our app, we could create a Form with a TextField and a Text inside but that would not work because SwiftUI wants to be kept up to date with what to store in that text field. As a rule to remember for the future: as views are a function of their state, a view can only show something if it reflects a value stored somewhere in the program.

We can therefore create a name property equal to an empty string , then inside our Form, create a TextField(<#T##title: StringProtocol##StringProtocol#>, text: <#T##Binding<String>#>) (yes, autocompletion when copied out is that complicated, in practice is a (_ title: String, text: BindingString) and then a Text("Hello World") to keep it simple.

To help the code compile we should now add @State in front of private var name = "" but that is still not enough because Swift here makes a difference between showing the value of a property somewhere and doing that along with writing any changes back to the property! This is a huge change compared to what was done before. This is called two-way binding. To do so, we need to add $ in front of the name call. Before running, change the Text to read "Your name is \(name)".

The absolutely amazing thing here is that it updates in real time! Also, when you hit Return on the keyboard, the keyboard is actually dismissed! Awesome!

Creating views in a loop

The type to create views in a loop from a piece of data is called ForEach. The Documentation entry for this is very small and reads just this:

A structure that computes views on demand from an underlying collection of of identified data.

It is clear enough, though. Create an array of strings in a constant called students filled with students from the Harry Popper movies and books, and an @State private var selectedStudent = "Harry" for example.

Inside the body property, create a Picker, which gets three parameters, a String Protocol “Select your student”, a selection: $selectedStudent and a closure that calls a ForEach(0 ..< students.count) that creates a Text(self.students[$0]). These are all closures and the syntax may look difficult but it is actually much simpler that what it could be because we are using trailing closures.

Here the students array doesn’t need to be marked with @State because it doesn’t change. The selectedStudent property, instead, needs to be able to change and it is therefore marked with @State. The Picker has a label, which is fully accessible, a two-way binding and a closure.

Now it’s time to clean the project and be ready to start anew, thing that closes today’s article!

Come back tomorrow for the second part!


That’s it for today.

If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).

If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s books from this Affiliate Link.

Anyways, thank you so much for reading!

Till the next one!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create your website with WordPress.com
Get started
%d bloggers like this: