In this blog post, I will log my journey in learning Swift and SwiftUI by following “The 100 days of SwiftUI”.
There are two reasons for this diary to exist and be publicly available: first, to share my thoughts about the language and the framework, and second, as a review of the learning process proposed by “The 100 Days of Swift,” which, as the title says, you have to follow for one hundred days. Moreover, as I am a person who already knows some programming languages, I was curious about how a new language is taught.
The day started with a brief introduction to the Swift programming language and how variables, constants and literals work in an assignment statement (the type inference concept is briefly introduced).
In Swift, there are both variables and constants. The former can be reassigned to a new value, while the latter cannot be reassigned (although you can probably change values inside them when objects come into play).
Regarding literals, this first day is about strings, multi-line strings, integer, and doubles.
Day 2
The following day, the course covered Boolean variables and string interpolation.
Regarding Boolean variables, the only novelty was the .toggle() method, which switches the value of the variable (similar to doing myVar = !myVar).
Regarding string interpolation (which is achieved through the () pattern), the course stated that it is more efficient than concatenation using the + operation. For example, "\(s1) \(s2) \(s3)" is faster than s1 + s2 + s3 because concatenation requires the use of a temporary variable for each “sum” except the last.
Finally, a simple exercise was assigned: a Celsius to Fahrenheit converter that required the knowledge acquired during the first two days.
Day 3
During the third day, we started looking into more complex data structures such as arrays, dictionaries, sets, and enumerations.
Two interesting facts about these data structures are:
Sets are incredible faster than array when you are looking for an item inside (.contains(…)) but they are not ordered and cannot contains duplicates
When working with enums you don’t have to write every time WeekDays because the compiler is capable of understating it by the variable type.
Day 4
In Swift, you can specify the type of a variable when you declare it. If you don’t do ii, the compiler will infer the type. Up to now, it hasn’t been necessary to specify the type, but in the feature it could come in handy.
Moreover, with type specification you can declare a variable or a constant without initiating it.
Finally, another checkpoint/exercise is proposed. In this checkpoint you have to declare a list with some duplicate, a set from the list and print both their .count showing that the set doesn’t have the duplicates.
Day 5
Conditional statement are introduced. if-then-else, switch, and ... ? ... : ...(ternary operator) can change the flow of your program.
Example with if-else if-else
Example with switch-case
In addition to this simple example, the switch-case statement can be use with the pattern matching (we wrote an article about it for the Python programming language. Check it out).
Moreover, Swift has a fallthrough statement. When a case block ends with the fallthrough statement, the program will execute the following case even though the condition doesn’t match.
Example with the ternary operator ... ? ... : ...
You can read the ternary operator as <condition> ? <value if condition is true> : <value if condition is false>.
Day 6
Now it’s time of iteration. for-in loop, and while loop
It is worh noting how ranges work in Swift: [min]...[max (included)] and [min]..<[max (excluded)].
while loops have nothing new with respect to other languages.
Finally, continue and break statement:
In Swift we can also have labeled statement. In this way we can break an outside loop when cycling inside a nested one.
Today was also a checkpoint day. Now we know all the basic stuff to write any computer program.
The solution for the fizz-buzz problem follows.
Day 7
With iterations done, we reached a turning point in studying Swift. Today we step up and start studying functions.
By default, Swift requires the names of the parameters when we call the function.
Returning values is as in many other programming language: return keyword and the type returned in the function declaration (with -> <Type>).
It is not true that the return keyword is always needed. When the return statement is the first line of the scope we can ommit the keyword.
During this day, tuples are introduced as a way to return multiple values from a function:
In Swift, tuples are like C’s struct (they can only hold data) but defined in the return type. Moreover, you can also deconstruct a tuple into variables (we can use _ to ignore some parts of the tuple).
Dive into function’s parameters
In Swift, it is interesting looking into how parameters are a first-class citizen when we talk about functions.
First of all, they are integral part of the function name. It means we can have functions with the same name but different parameters’ name.
Then, we can have two names for a parameter: one used inside the function body and the other used in function call (which could be _ to have nothing)
Day 8
In Swift, we can also have default parameters. That is done adding the default value in the parameter declarationn func f(a: String = "default string") { }.
Functions can also throw exceptions and that must be added to the function definition with the throws keyword.
To call a function which could throw an exception we have a couple of ways: do-try-catch block or try!.
Finally, to test functions in a more real use case this day’s checkpoint requires to find the integer square root of an integer number.
Day 9
After functions, it is time to go functional!
A function can be assigned to a variable and called from that variable. That is usefull when you want to let the user define a behaviour to call later.
In the example, bCaller is called closure (a.k.a anonymous function).
To accept some values as input and return, we have to specify it in the closure with {(<parameter list>) -> <returnType> in } (which is also the type of the function).
In a closure, we can avoid using parameters name and use $0, $1, $2, ... and inline the function definition (having an anonymous function) instead. That is called trailing closure syntax.
Obviously, function can be accepted as parameter by other functions (we can say that is what functional means).
One thing I am not really happy with is how are handled multiple closure as parameters
Finally, as checkpoint, here it is a program that exploits the power of closures.
Day 10
After functional, object-oriented programming joins the game.
We start with structs:
By defualt a struct instance is constant. It means you cannot change the value of its variables even if they are marked as var.
In the example above, when we do vacationRemaining -= days, the compiler will complain with the following error message: Left side of mutating operator isn't mutable: 'self' is immutable.
To change this behaviour we have to explicitly mark the function as mutating.
However, when we declare the variable holding our instance it changes if we declare it as let or as var.
When we declare let mary = ... we cannot invoke any mutating function. We will get the following error: Cannot use mutating member on immutable value: 'mary' is a 'let' constant
Notes on properties
Computed property
That is a fancy name for a getter and setter. For the setter the new value is hold by the automatic newValue parameter.
It is worth notice that, computed properties can only be var.
Observed property
In addition to getter and setter we have also didSet and willSet to observe how a variable is changed or is goining to change.
Initializer
Finally, let’s talk about how to initialize our structs.
By defualt we have a generated initializer which takes in input all the properties of our struct.
Before calling any struct’s method inside the initializer we have to initialize every property.
Day 11
Access modifier
By defualt the access to struct’s members are public. There are other two access modifier:
private: only by struct
fileprivate: only in the same file
We can go further and use private and fileprivate also by setter.
private(set) var ...
An interesting point is that Swift cannot generate the initializer when we have a mix of private and public attributes.
Static members
We can declare static members for our structs.
In a static method we can access to “self” through the keyword Self (note the capital S).
Checkpoint
Today has been also a checkpoint day.
Today’s task was to define a struct for a car which can change gear.
Day 12
Structs can hold data and perform operations through functions but OOP is much more. For this reason we need classes.
In addition to structs, classes are defined by:
Inheritance
Not having an auto-generated initializer
Copying an instance of a class means copying its reference
De-initializers
The possibility to change properties of an object declared as constant
Copy means shallow copy
We have to implement our own deepCopy function to overcome this.
Inheritance
Regarding inheritance, Swift works as many other programming languages.
Initializer
Initializers are a bit more complicated than struct’s initializers because inheritance could be in between.
It is worth noticing that in ChildClass’s initializer we must initialize its own attributes before calling ParentClass initializer.
De-initializer
We can define the behaviour when an object is destroyed. In Swfift we don’t have to manage the memory (no new and delete keyword). So, an object is destroyed when the last variable, pointing to it, is unreachable (out of scope ({...})). Swift uses Automatic Reference Counting (ARC) to keep trace of which objects are still reachable by some variable.
Variables or constants?
When we declare a constant object (let a = A(a: 0)) we are saying that a will point to that area of memory and cannot be changed (a = A(a: 1)). The area of memory holding the object is not constant and can be changed (if properties are declared as var and not as let).
This brings another difference from structs. Classes don’t have the mutating keyword for functions.
Checkpoint
Classes can be challenging. To be sure to have understood how they work an exercise is proposed: a class hierarchy for animals.
Day 13
Protocols
Next and last step in OOP is learning interfaces (Swift calls them protocols).
Protocols can require also to declare variables. For each one you have to declare if they are only readable {get} or readable and writable {get set}
Opaque return types
Sometimes we cannot tell to the compiler what type we are returning from a function because it will complain, maybe, without reason.
The solution is telling Swift that we return some of that type
The some keyword let the compiler to know the real type reuturned but from our perspective it is only an equatable.
The some keyword can be read as: “we are returning a specific type of equatable but we are not saying which one”.
SwiftUI uses this feature a lot.
Extensions
With extensions we can add function(alities) to any type.
Moreover, if we implement a custom initializer for a struct inside an extension, Swift will keep the default initializer and of our custom one.
We can extend protocols as well. In this case we can specify implementation for functions in protocols accessible everywhere. In the example before, we can specify the method estimateTime as an extension of the protocol Moving in order to provide the default implementation return distance / speed.
Checkpoint
Also today has been a checkpoint-day. The challenge was about modelling a protocol for a building and implementing it for two structs.
Day 14
Last topic about the Swift programming language: the optional type. In Swift, we can mark any type as an optional type, which means that the variable holding that type could be an instance of the type or nil (null, None, or nullptr in other languages).
In Swift, to say that a type can be nil we specify it in the type specification with a question mark: Type?.
Unwrapping optional values
We have a couple of solutions to unwrap optional values:
if-let
guard-let
Nil coalescing
Also known as: default value. It is achieved adding a doubble question mark ?? after the use of the optional value.
It is worth noticing that you can chain default values: let a = f1() ?? f2 ?? "default".
Optional chaining
We can use optional inside optionals.
You can read it as: “unwrap the value and continue the processing. If any step fails unwrapping, then fallback to the default value”.
Optional instead of exception
We can catch an exception and return a nil instead of handling the exception. To do so, we can use try? before calling the throwing function.
We can also let the program crash. When we use try! f() the program will crash if any exception is thrown in the function called after the try!
Checkpoint
Also today was a checkpoint-day. The challenge was about coding a function that handles optional values in a single line of code.
Day 15
Today has been dedicated to a recap of the foundamentals learned so far. Nothing new has been added. From tomorrow we move into SwiftUI and app development.
Day 16
Today we start iOS app development with a running example. WeSplit is an app that allows to split the cost of a launch or a dinner among people.
Structure of a SwiftUI app
When we create a new iOS app in XCode, the IDE will genereate a couple of file for us.
The main file is the file with the name of our app. It will contain the following code
This struct will live for the whole time our app will run.
The interface is handled by ContentView
For now everything will be managed by ContentView. At a first glance we can already see a lot of stuff we have already see in the last days.
We have:
a struct representing our view which implemnts the View protocol
a computed property called body of type some View
Then we have new stuff:
a VStack
an Image
a Text
a call to the padding() method
Last thing is the #Preview statement which initialize an object of type ContentView which will be showed in XCode without running our app. This feature is called canvas.
Form
A Form is a graphical element which can be used as container for other elements (usually, for data input from the user). We can separate sections of the forms, grouping them inside a Grouping object.
Navigation bar
Container which allow the navigation between views. We surround our Form with a NavigationStack and add some properties to it applying them to the form.
App state
What a view presents depends on its state.
In Swift, structs cannot change the value of their variables outsie mutating func so we cannot have something like this:
The solution is using a property wrapper for our varibale called State.
Binding
We want our user interface being able to change the state of our app. When we write a string as input of a TextField we can link it to a @State variable but that is not enough. We have to tell to SwiftUI that we are binding that state to the graphical element which will change. To do so, we pass $varName instead of varName
The dollar sign means: be able to read and change the value in the state variable.
Loops
We can create views in a loop (maybe one for each iteration).
We can use it in a PickerView to select an element from a list.
We can see things are becoming complex:
We define a NavigationStack
Inseide the NavigationStack we insert a Form
Insider our Form we insert a Picker which has:
a label (“Select your student”)
a varibale where store the selction ($name)
a list of views (Text) which represent the possible selections. This list is generated with a ForEach “loop”
we iterate over students
we tell to the ForEach how to uniquely identify each element (id parameter (we use \.self which means each string))
we create the view Text with text the first element of the closure (the string itself)
Day 17
Let’s start developing our WeSplit application. First of all we need three state variable: checkAmount, numberOfPeople, and tipPercentage. Then we need an array of possible tip percentages. This is not a state variable because it will not change.
Finally, we declare the body computed variable which will be computed every time a state variable changes.
First iteration
A new element is the access to some Locale value. It holds the identifier for the current currency (“EUR” in my case). It is handled with nil coalescing and a default in case of errors.
Another addition is the definition of the keyboardType to the TextField object.
Second iteration
Next step is adding a picker for the number of people eating at out table.
We start declaring a Picker which will be binded to the numberOfPeople variable and will show # of people as text. In addition we can change how the view to select is shown. Adding a NavigationStack around our Form we can set the pickerStyle to .navigationLink to show a new “window”.
Third iteration
Then, we define our graphical element to input the tip percentage. We use again a Picker with the pickerStyle as .segmented. Moreover, we add a header text for the section holding this new picker to tell the users what they have to do
Fourth iteration
Finally, we compute the total per person. We create a new computer varible and inside it we add the computation which result will be shown in the last Text
Fifth iteration
Let’s fix the “bug” of the keyboard not disapearing after we edited the check amount.
We add a boolean variable amountIsFocues and we mark it as FocusState. Then, we bind this variable to the focused property of the TextField which allows the user to input the amount. Finally, we add a toolbar to our Form (which will be showed in the NavigationStack) with a button to toggle the focus.
Day 18
Today is focused on trying adding functionality to WeSplit without any help. The tasks are:
Add a header for the last section
Add another section with the grand total
Change how the user select the tip percentage. From the segmented view to a list view (as the number of people)
Day 19
Today we have built a unit converter app from scratch without any guide.
The task required to look back at the code wrote last days and study the Measurement library provided by Swift.
The result is an app which is capable of converting any type of lenght unit into another.
Day 20
Stack
In SwiftUI, stacks are foundamental. There are three kind of them: VStack (vertical), HStack (horizontal), and ZStack (z-axis/deep).
They help us in aligning items in a direction. Moreover we can specify the spacing and the aligninment between the elements inisde.
In addtion to stacks, the Spacer has been introduced. It helps spacing element inside another view (a stack for example).
Frames
In SwiftUI, frames are the geometry entity behind each graphical element shown on screen.
For each one, we can set the frame with width, height and min and max variants of them.
Color
The Color entity is the thing that allows us to make our apps colorfull. We can use predefined color (which can automatically switch when the user change from light to dark and vice versa), make our own color, or even use materials.
Linked to colors, there are gradients. In SwiftUI we have three kind of gradients to use: LinearGradient, RadialGradient, AngularGradient. For each one of them we can specify the colors and also the stops for the gradient.
Buttons
Buttons are what users interacts with. As simple as possible, a button is made of a string label and an action to perform when it is clicked. The action can be a method and not an anonymous function.
Images
The way to show images in SwiftUI is using the Image element. It allows us to show an image present in the Assets folder or a systemImage from the SFSymbols app. We can declare if the image is just decorative (Image(decorative: "<imageName>")) to help screen reader in better understanding what they are reading.
Label
A Label is an object that can be used inside the label: {} view of a Button instad of Text. With Label, iOS is capable of performing changes if needed to it without the need to specify anything.
Alerts
To show a message to the user we can use alerts. In SwiftUI we don’t declare a object of type Alert and the present it in someway. In SwiftUI we declare a State variable which will tell if we are in a state presenting the alert or not. Then we add to our view the property alert biding to it the state variable.
Day 21
Images from file
We can import images into our XCode project. More precisely, we import them into the Assets directory. Each image can have three dimesion: 1x, 2x, and 3x. We can automatically import all the images we want and XCode will understand their dimension based on the file name. fileName@2x and fileName@3x` will create a unique asset with two images (2x and 3x).
Guess the flag
Guess the flag is a simple game which asks to the user to indetify the correct flag among three options. Today’s effort is concentrated into creating the user interface and the simple logic of the game (except for storing the score).
In SwiftUI every bit of the user interface is made with code, which means that things get confused when a lot of different modifier are applied and the logic is, somehow, hidden.
Day 22
Today we have completed the “Guess the flag” app as a challenge. There were three tasks:
Add a property to store the current score
Have a more descriptive text when the user chooses the wrong answer. Telling them what was the flag they picked
Create the concept of “game” which lasts for 8 attempts. Then, a final alert is shown when the number of attempts reaches zero.
The first task is trivial. We add a new @State private var score = 0 and update it in the flagTapped() function.
Then, we have to remeber if the user picked the wrong answer. The solution I tought is: having a state variable holidng if the user picked a wrong flag and, if so, which one. I implemented it with an optional int: @State private var wrongAnswer: Int? = nil. When the user select the wrong flag, in the flagTapped() function we save the number parameter in wrongAnswer and when we show the alert we check if wrongAnswer is not nil. Then, when we ask a new question, we reset the value to nil.
Finally, for the last task we need a new boolean variable to bind to a new alert and a counter to keep track of the number of attempts the user already tried. We updated both these two state variable in the flagTapped function paying attention at which showing variable we set ti true.
Day 23
Today we deep dive into technical stuff answering some questions on why SwiftUI works like that.
Why structs?
Structs are simpler data structure than classes. They forbid inheritance, which means that they cannot grow without bounds inheriting “useless” attributes. We built from scratch every view we want to show to the user composing it with other views (present in SwiftUI or made by us).
Modifiers order
When we apply some modifiers to our views the order is important and is red from top to bottom.
Moreover, modifiers stack on top of each other.
Why some View?
We don’t want to specify which specific type of view the body variable has. That’s because its runtime type is pretty complex.
When run in a simulator in a simulator and the output of print is ModifiedContent<ModifiedContent<Button<Text>, _FrameLayout>, _BackgroundStyleModifier<Color>>. This is a complex type we don’t want to specify (and change as soon as we change a modifier).
Conditional modifiers
In our body variable we can have if-then-else statement to declare different elements but when we cannot use it to declare different modifiers. The solution is using the ternary operator (<condition> ? <true value> : <false value>).
Apply modifiers to all child element
We can apply a modifier to an “environment”. For example a set of Texts inside a VStack can be modified applying an environment modifier to the VStack. It is important to notice that not every modifier are environment modifier.
View as constants
We can declare views as let aViee = Text("Some Text") and use aView in our body variable. Keep in mind that binding in this case could be tricky and we have to create a stack or a Group if we want to add multiple elements inside our attribute. A possible solution is creating a computed variable with the @ViewBuilder attribute.
Extract and compose views
We can define our own views and use them anywhere.
Custom modifiers
We can declare structs which implemnt the ViewModifier protocol to create our own modifier. Then we can apply them to any view passing an object of that struct to the modifier modifier. To make things easier we can define a function which apply our modifier in an extension of View.
Custom containers
We can even define our stack. For example we define a struct representing a GridStack. It is a bit more complex than everything else seen so far but it is understandable.
There are a couple of things which are worthy to notice:
GridStack<Content: View> is the definition of a generic or template. The content closure will return a something which implements View
@ViewBuilder means that the resulting view will be build taking into consideration every declared element. That is important because we can avoid to embedd the Image and the Text inside a container in our ContentView. It is worth noticing that the padding modifier applied to content will be applied to both Image and Text
Day 24
Today, three challenges await us. We have to modify our previous projects with what we learned yesterday
WeSplit with conditional modifier
The first challenge is changing the color of the totals in WeSplit. We want a red tint when we select a 0% for tip.
Custom view for flag image in GuessTheFlag
The second challenge is creating a FlagImage struct to represent our flag images with the several modifier to make it fancy.
Custom modifier for a large blue title
Last challenge is creating a custom modifier to create a large blue view.
Day 25
Recap day. Today we consolidate what we learned so far and add some small bits to it.
Custom bindings
We have seen how bindings works ($varName). What SwiftUi is creating a Binding object for us which bheaviour is the following
Challenge - RockPaperScissors
The 25th day challenge is a rock-paper-scissors game. The task are:
Each turn the CPU randomly choose between the three options
Then the player tap on their choose
An alert show if they won or loose
The score is stored. A win is a +1 and a loose is a -1
The game lasts for 10 rounds. Then the final score is shown and everything resets
My solution is, maybe. a bit overpower. I wanted to just test if a symbol (rock, paper, and scissors) is greater than another to see who won. To do it in this way I implemented the protocol Comparable for the Symbol enum.
Day 26
Today we start a new project. An app which uses machine learning (ML) to suggeest at what time the user should go to sleep given some inputs. Before entering the ML world in Swift with CoreML we have to setup our app user interface.
Stepper
Stepper is a new graphical element which allows the user to input a number incrementing or decrementing it. The following code will generete a stepper binded to sleepAmount, which can range between 4 and 12, and each step is 0.25.
DatePicker
Another element is the usefull to let the user select a date and/or time.
We can also add a range in between the date can be selected. And the range can also be an open range (to select only future or past date for example)
Date components
It is worth deeping dive into date components (DateComponent() objects). They are objects holding info about hours, minutes, etc. etc. inside a date. They can be used to build a date from scratch Calendar.current.date(from: component) or can be retrieved from a date (as optional value) Calendar.current.dateComponent([.hour, .minutes], from: .now).hour.
Create the ML model
Our app will predict the time at which the user should go to sleep with a ML model. First of all, we want to create such model to be used later in our app.
To create a ML model for CoreML we can use CreateML, a MacOS app that let us to create ML models through a graphical user interface.
It let us to choose among many types of ML models. We are interested in a Tabular regression because from a dataset in a tabular form we want to forecast a value. Then we setup our parameters (input dataset, target value, features, …) and finally, we just click the Train button.
What we get from CreateML is a .mlmodel file that can be used in our iOS app.
Day 27
Today we use our ML model in the BetterRest app. It is very simple:
Import the .mlmodel file into our project
Reference it as any other Swift class
Behind the courtain, XCode automatically creates a class for each model in our project.
Using this class is very simple:
Create a variable holding some configuration (it can be empty)
Create an instance of our model passing the configuration
Call the method .prediction and pass the input data necessary for the model
In our BetterRest app, we do a bunch of stuff on top of the three points to parse the dates and showing an alert. It works in this way:
After improving the user expierence and the user interface the final result is:
Day 28
Today, we have three things to do to improve BetterRest:
Replace the VStacks with Sections
Replace the Stepper to select the number of cups of coffee with a Picker
Remove the calculate Button and automatically show the predicted bedtime
It is worth noticing that this new layout works great also with the dark mode activated (see image 4).
Day 29
New day, new project: the WordScramble app presents a word to the user which has to find other words inside it.
Maybe more important than the app itself, there is a new graphical element: List. It allows to present (in both static and dynamic way) some content to the user in a list view. If you think about it, almost every app you use everyday has at least one list inside it.
List
A List is similar to a Form (at least its basic functions) with the possibility to create it from a array:
Bundle
Every app lives inside a container and, usually, every app uses some static files (no source code). To access those files we have to get the file path where our app is located. We don’t want (and can’t) try decoding the actual path to our directory. Apple gives us an access to them through the Bundle class.
String utils
In this project we are going to work with strings. We already saw some methods for strings but, for this instance the next one will come in handy:
.components(separatedBy: ) return a list of strings each one was separated by some other string in the initial string
.trimmingCharacters(in: ) return a string without the characters passed as input
In addition, we will use the iOS’ spellchecker. It is an “old” piece of code written in Objective-C and it uses some nasty types. I leave here the example:
Day 30
We start building the user interface for our new app. First of all we declare a List with two Sections. The first one is a static row with an input field for the new words user can insert, and the second one is the dynamic part with rows which replicate what the user added. We also add an action when the user press the submit key on a keyboard defining the attribute onSubmit and passing an action to add a word. Moreover, when we add a new word, we embrace the .insert method inside a withAnimation method, This creates the animation of insertion automatically for us.
Load file from bundle at startup
We want to load a txt file from our bundle at the startup of our app. We saw yeaterday how to read files from our app’s bundle and today we put that in practice:
To execute this method when our view appear we can add another attribute to our List view: .onAppear(perform: startGame).
Word validation
Next step is validate words give by the user. We have to check three conditions:
is a new word (i.e. not already added)
is a word made of letters from the root word
is a real word from the english dictionary
We write a method for each check plus an utility method to show an error message if something fails and we add the checks before adding the new word.
Day 31
As always, at the end of a project, a small challenge is in front of us:
Disallow answers shorter than three letters
Add a restart button
Compute a score/points
These three challenges are nothing new:
Day 32
Today we start looking into animations.
Thanks to SwiftUI we can automatically animate any element in a simple way
This snippet of code creates a button and define a State property (animationAmount) which is used in two modifier: scaleEffect and .blur. The ,animation property listen for changes in animationAmount and automatically creates an animation for us. The change happens when the user tap on the button (animation += 1).
We can make even more complex animations adding modifiers to our .animation’s style:
Animated bindings
We can animate an element binding the property which trigger the animation to another element:
Explicit animations
We can also say which value changes we want to animate sourraunding the value change within the withAnimation method
Day 33
We continue studying animation. Some days ago we learned that the order of modifiers matter. It is the same for the .animation modifier. It will animate only the other modifiers declared above it. This allow for a fine controll of the “animation stack”. In the example below we apply two different kind of animation (a .default and a .spring) to two different modifiers:
We can even animate gestures (that’s a bit of a foreshadow)
We can create also quite complex animations:
We can also present our elements with some animation. By default we have a fade-in and fade-out but, we can specify the .transition modifier to tell SwiftUI how we want our view to appear:
We can go further and create our own transition style. A transition style is an object of type AnyTransition and it uses ViewModifiers to specifiy the active and identify status.
Day 34
As challenge, today, we add some animation to GuessTheFlag. We have to:
Add a spinning effect to the selected flag
Add a fading effect to the not choosen flags
Add a scale-out effect to the not choosen flags
To reach these targets we have to change our code from day 22. In particula we define three State property which contains the value for our animations:
I used an array to hold this information because values change based on which flag the user tap (0, 1, or 3)
Then, we add some modifiers to our flag-buttons and change the correct value in our arrays:
Finally we reset the values of fadeAnimationValue and scaleAnimationValue in the askQuestion method:
The final result is shown in the gif below.
Day 35
Today we are on our own. We have to build an app from scratch. The requirements are:
User can insert a number representing the multiplication table they want to study
User can insert the number of question which the system will propose
The system presents to the user the questions
The system presents the final score (correct answer over number of questions) to the user
To tackle this problem I started from defining a view with some states representing the data the user will insert, and a gameStatus which can be either settings or paly. Then, based on this gameStatus the app present two view:
FormSettingsView which require to the user some settings to play and a button to start playing (switch the gameStatus to play)
PlayView which presents to the user the questions and keeps track of the score.
Day 36
Today we start learning how to build more complex apps.
Observable objects
Let’s start seeing how to handle set of data holds together with structs and classes.
In this first example everything works as expected. We can change the value of firstName and lastName and the view will change accordingly. There is a problem with this approach: we cannot share the user variable with any other view because it is an instance of a struct. The solution is easy: change struct with class.
But now, it is not reactive anymore. If we try changing the first name or the last name, our view does not change. That’s because we are not recreacting a new object when we make a change (that was happening with the struct).
Fixing this issue is easy. We add @Observable above the clas declaration:
@Observable tells swiftUI to keep track of changes happening inside the class and reload the view which depends on that class. @Observable is a macro which “rewrite” our code before compiling it to give us some fuctionality.
Presenting new views
We can present new view in a similar way we present alerts. Ataching the .sheet modifier to our view and returing the view we want to show in the content parameter. We can obviously pass data to this new view and to dismiss it there are two ways: swipe down the view and call the dismiss() function given to us by @Environmen(\.dismiss) property wrapper.
Deleting items from a list
Next thing to step up our apps are the deletion of items from a list. It is achieved by adding the modifier onDelete to the ForEach insider a List which accept a function which takes as input an IndexSet representing the offset from where delete the item (which can be conveniently passed to remove(atOffset: ) method of an array). We can even add the “Edit/Done” button in a simple way.
AppStorage (or UserDefaults)
A way to store user data is using the @AppStorage wrapper (which is a wrapper itself around UserDefaults). This allow to remember soma data even if the app is closed.
Keep in mind that AppStorage is good to storage small amount of data and not big object.
Encode and decode data
We can save more complex data in UserDefaults. We just have to convert it in a string format (JSON for example). As long as we want to store simple structs (made by strings, integers, booleans, doubles, …) we can just add the Codable protocol to our string and invoke the encode method of the JSONEncoder class.
Day 37
Today we start building the iExpense app for real. We need:
A list to show our expense
A form to insert new expenses
Let’s start with task number one: we can define a new struct which holds our expense data:
There a couple of new things here:
: Identifiable means that our view conforms the the Identifiable protocol
id = UUID() is necessary to conform to the Identifiable protocol. The UUID() generates a uniqui identifier for each instance of our struct.
Then we need a class which encapsulate our list of expenses:
And the we can define our ContentView:
It is worth noticing that in the ForEach statement to build the list we haven’t declared the id: . That’s because our ExpenseItems conform to the Identifiable protocol.
Moreover, we added the logic to present our AddView. The AddView is a view we have declared in another file (AddView.swift)
This implementation “works” but it is missing of a key requirement: data persistency.
To achieve data persistence we have to change our models (ExpenseItem and Expenses)
The ExpenseItem has to conform to the Codable protocol in order to be encoded and decoded automatically.
Moreover, we change our Expenses class in order to:
Automatically save the items each time it is modified
Initialize from the data saved in the UserDefaults
Day 38
Three challenges lie ahead for today:
Use the user’s preferred currency instead of the hardcoded "EUR"
Change how the amount is shown: green if below 10, yello if below 100, and red otherwise
Split the list in two sections: “Personal” and “Business”
The first two challenges have been quite easy. They required the application of something we have already seen other days. The last one required some more thinking. My solution modifies the removeItems method called on onDelete. Now it requires the type of the section and then convert the offset to the expenses and then remove the expenses which match.
Day 39
Today we start a new project but, as usual, we learn some new stuff before starting our real project.
Images
First of all, we start learning how resize images. There are a couple of modifiers to change how the user sees an image:
resizable()
clipped()
scaleToFit()
scaleToFill()
containerRelativeFrame(...) {size, axis in ...}
ScrollView and LazyStacks
A ScrollView is a viw that can be scrolled by the user. It always has a stack inside which can be Lazy if you want that each view is built just before appearing on the screen. Otherwise, every view will be built at the instatiation of the ScrollView.
NavigationStack and NavigationLink
We can use the NavigationStack view to build element which “open” new views. Inside a NavigationStack we can add a NavigationLink to create a cliccable element which create a new View and push it on top of the NavigationStack automatically builidng the “back” button.
Hierarchy of codable structs
When we have complex Structs (with arrays or other structs as attributes) we have to ensure that every other Structs is Codable/
GridView
We can layout our elements in a grid way. We need an Array of GridItems (which specify the repetition in the axis which is not described by the ScrollView) and a ScrollView with a LazyVGrid or LazyHGrid inside.
Day 40
Today we start building our MoonShoot app. We start from the data models: Astronaut and the way to decode them from a JSON file.
We start writing our struct which reflects the structure of our Astronauts in the JSON file:
It implements the Codable protocol to be decoded from a JSONDecoder.
The decoding part is implemented in an extension of Bundle.
Our app has another data type: Missions. As always, we start writing its struct reflecting the json file from which they will be instantiated:
And then we need a method to decode the file. We could copy&paste the decode method we wrote earlier but the only thing which change would be the return type. A better solution is using generics. Let’s change the decode method:
Now we can return any type conforms to the Codable protocol.
It is importart to notice that to use this method we need to specify the type before calling the method otherwise Swift would not know how to instantiate T.
This implementation has still some flaws. In particular mission’s launchDate. Now, it will be parsed as a simple String but that it’s a bit awful. The solution is adopt the Date type that Swift provides.
Now, our decoder has some problem understanding how to decode the launchDate properties in the JSON file. We have to specify the format in our decode function:
Finally we can move on building our view. We will use a grid to show the missions
There are a couple of utilities added to make this code more readable:
computed properties in Mission
custom background colors
The first additions regard the Mission struct:
They are three computed properties to simplify the presention of a mission removing the work from the view.
The custom background colors are implemented as extension of ShapeStyle (because Color is a ShapeStyle):
Finally, it is worth noticing the .preferredColorScheme to force the color scheme to be the dark mode.
Day 41
Today we add two more views. The first one presents the mission and the second one presents an astronaut.
Day 42
Three challenges lay ahead today:
Add the mission date to MissionView
Move some complex part of MissionView in its own View
Present the list of missions both as grid and list
The first two challenges are easy to complete.
The last one is a bit more tricy. We can implement the functionality in two ways:
With an if statement we can switch from the ScrollView to a List
We can use the already working ScrollView as a list.
I have choosen the second one.
First of all, we need a property to store which presention mode we have: @State private var isGridView = true. Then we have to change the columns of our grid making them filling the whole screen when the property is set to false. We can change the columns property in this way:
Then we can add a button in the toolbar to toggle the isGridView property.
Day 43
There is a “huge” problem in using NavigationLink: it will immediately instantiate the view it has to present. This is not a problem when we have just one view but it can slow down our application if the new view require some time to be instantiated.
There is a better solution: navigationDestination(). To use it there are two steps:
Define the navigationLink with a value
Add the navigationDestination modifier
There is a requirement for the navigationDestination() to work: the value/for must conform to the Hashable protocol.
Day 44
Programmatic navigation
We can create the navigation stack programmatically. We have to bind an array to the NavigationStack and then we have just to change the content of the array to trigger the nagiationDestination(). An array because there could be more than one view that we want to show.
We can go further and change the path state in other views. For example we want to go back to the first view pressing one button.
NavigationPath
How to handle path with different types? When the user touch the NavigationLink there isn’t any problem:
What about the programmatic way? As we have seen before we have to bind an array to to the path attribute of the NavigationStack but in this case we cannot have an array of both Int and String.
The solution is using a NavigationPath which behaves as an array but accepts any type.
Store the NavigationPath
We can save the NavigationPath for remembering where the user left.
First of all, we create a class that holds the NavigationPath and save it on file when it change and read from file at initialization.
It is worth noticing that we cannot save the NavigationPath directly because it could have non Decodable objects inside. We have to pass through its CodableRepresentation/codable.
The rest of the code is similar to other codes we wrote today.
Day 45
Today we look at how we can change the appearence of the neviation bar. There are some modifiers for the bar and the title:
navigationBarTitleDisplayMode() -> change how thw title is presented: inline or large
toolbarBackground() -> set the background (color) after the scroll
toolbarColorScheme() -> set the color scheme: dark or light
toolbar() -> set the visibility: visible or hidden
Each modifier can specify the for parameter to tell at which component the modifier has to be applied: navigationBar, tabBar, or bottomBar.
We can change which buttons appears in the tollbar thanks to the toolbar modifer.
There are a couple of ways to add new buttons in the toolbar:
Simply add Buttons in the toolbar’s closure
Add a ToolbarItem() with inside the button. It allows to specify the placemente of the item with ToolbarItem(palcement: .)
Add a ToolbarItemGroup with inside some buttons. As the ToolbarItem, it allows to specify the placement for the group
Last thing, we can make the title editable. It only works in .inline display mode and the title need to be binded to a state property:
Day 46
Today we have three challenges to complete:
In the iExpense project, we have to update the navigation with a NavigationLink instad of a sheet
In the iExpense project, we have to change how the user insert the expense name, moving the textField to the title
In the Moonshot project, we have to use the NavigationLink
The first two challenges can be completed with a few change:
The next challenge requires some more changes.
First of all we have to make Mission, CrewRole, and Astronaut conform to the Hashable protocol. Then we can change the NavigationLinks to make them use the value propoerty. Both the navigationDestination must be set in the first view (the one with the NavigationStack)
Day 47
Today we work on an app to track activities. It is a challenge so everything is written by us. The requirements are:
The user can add new activities
Each activity has a title, a description, and the number of time it has been done
The user can see the list of activities in a list
The user can edit activities already added
My approach is: a list view and a create/edit view.
First of all the models:
Then we can move into implementing the views:
The idea and the functionalites are similar to iExpense.
Day 48
Today we aren’t studying anything technical but listening to a talk.
Day 49
After a little break go deeper into data. At this point we are goining to retrieve data from the Internet.
First of all we need to define our data model. We are going to collect some data from the iTunes service (songs and collection).
Then we build our interface as usual but with a little twist:
As you can see we have introduced a couple of new things:
func ... async declares an asynchronous function
.taks {...} is a “context” from which we can call asynchronous functions
await is a keyword to call asynchronous functions
UrlSessions allows us to get data from the internet
We can also show images from the Internet. We cannot use an Image but we have to use an AsyncImage and there are three ways to use it:
This first method is the most simple but also the less customizable. We can only change the scale and anything else.
This second way gives us the image after the loading (during while the preview is shown). In this way we can change is as we like.
The third way gives us a way to handle errors:
Last thing for today is form validation. We can apply the disable modifier to a section of a form do disable all its content (for example the “submit” button) and binding the condition to a computed property.
Day 50
Before starting diving into our app we need to understand better a couple of things.
Codable for Observable classe
When we add the Codable protocol to a class it will automatically allows us to generate string representation of objects of that class. For example:
Will print:
Which is not the best looking JSON. Swift gives us a way to changes to automatic behaviour. We have to define an enum named CodingKeys which conforms to the CodingKey protocol.
Now the resulting JSON will be:
Haptic effects
There are two ways of interacting with the Haptic Engine of an iPhone.
The first one is simple. Just add the modifies sensoryFeedback to a view, add the parameter SensoryFeedback (an enum to configure the type of feedback) and trigger (a state which changes will trigger the feedback).
Another solution is configuring the Haptic Engine with much more details:
Cupcake corner
Finally, we start implementing our app: Cupcake Corner.
We start defining our data model:
And then moving into the form to customize our order:
Day 51
Today we implemnt some neet functionality of Cupcake Corner:
Form validation for the address
Data upload and request from a web service
First of all let’s create the two AddressView:
We use @Bindable to tell SwiftUI that order is a bindable variable (declared as @State somewhere else).
Then we have the .disabled(order.hasValidAddress == false) that reflects some additions made to the Order class:
Then we can move on the CheckoutView which will handle the connection to the webserive to “place” the order:
Besides an AsyncImage there is the placeOrder function which upload the JSON version of our order to regres.in (a web serice to test clients). And then, we decode the response in another object of type Order (just because regres.in works in this way (it sends back the json we send to him)). The result is shown in an alert.