Tap Me!
Level: Beginner-Intermediate
Hello guys, today we are going to create a reactive app, and you are going to learn a new thing called "Property Wrappers".
As we all know that Apple loves structs and structs are immutable in Swift. So to overcome this issue, Apple invented a great thing called "Property Wrappers".
Let's dive deep and understand what's going on.
Whenever a struct gets initialized, all the properties inside it should have some values. Since all the views created using SwiftUI are structs, the properties once initialized cannot be changed.
Now that we are building an interactive app, the User Interface (UI) needs to be updated accordingly.
I'm going to demonstrate the problem and the resolution that Apple prefers by building this app.
So without further due, let's get started.
Open Xcode and create a new project named "Tap Me".
As the entry point for our app is ContentView.swift file, open it and start writing some code.
First of all, we are going to enclose our view inside a NavigationView and provide a title.
import SwiftUI struct ContentView: View { var body: some View { NavigationView { Text("Hello World") .navigationBarTitle(Text("Tap Me! App")) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
User Interface building in SwiftUI is declarative and what we want is a Text and a Button to be one after other vertically.
So we will put them together in a "VStack" which is a vertical stack of Views.
There are three types of stacks.
- HStack (Horizontal Axis)
- VStack (Vertical Axis)
- ZStack (Normal Axis)
Let's break down the UI Components hierarchy we want to create our app.
NavigationView VStack HStack Text Button
Now that we have all the components broken down, let's write it down.
import SwiftUI struct ContentView: View { var body: some View { NavigationView { VStack { HStack (spacing: 16) { Text("Tapped") .font(.subheadline) Text("0") .font(.largeTitle) Text("times") .font(.subheadline) } Button(action: { //Button Tapped }) { Text("Tap Me") .font(.title) } } .navigationBarTitle(Text("Tap Me! App")) } } }
Now that we have created the UI, it is still not alive. So, let us give it life by making it reactive.
Our goal is to increase the value of the counter by one on the button click. So we will need an instance variable which will keep track of the value of the counter.
var count = 0
Now we are going to write the definition for the "buttonTapped" function.
func buttonTapped() { //TODO:- Increment the value of counter by 1 }
As soon as I change the value of the counter variable, Swift starts complaining that "'self is immutable" which means that the properties inside a struct once initialized cannot be changed.
Here comes the "Property Wrapper" to show it's power.
There are several "Property Wrappers" in Swift 5 but what we are interested in this article is "@State".
@State var count = 0
So, I'll just be adding "@State" before the declaration of the counter variable and BOOM, the code compiled successfully.
So what is happening behind the stage? This is what Apple says.
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.
reference: https://developer.apple.com/documentation/swiftui/state
Probably you are scratching your heads right now saying "What the heck is The single source of truth". Believe me guys, the same thing happened with me too when I was just starting to learn SwiftUI.
When an object which is responsible for the changes in a specific UI component, it should have a single reference.
The pictures below will make you understand the "State" better.
Now that the concept of "@State" is clear, let's get back to our app.
Now that our counter is working as we wanted, we also need a button to reset the value of the counter.
So let's create a "Button" with a few other lines of code and place it in the "NavigationBar".
import SwiftUI struct ContentView: View { @State var count = 0 var resetButton: some View { Button(action: { self.count = 0 }) { Image(systemName: "arrow.clockwise.circle") .imageScale(.large) } } var body: some View { NavigationView { VStack { HStack (spacing: 16) { Text("Tapped") .font(.subheadline) Text("\(count)") .font(.largeTitle) Text("times") .font(.subheadline) } Button(action: { self.buttonTapped() }) { Text("Tap Me") .font(.title) } } .navigationBarItems(trailing: resetButton) .navigationBarTitle(Text("Tap Me! App")) } } func buttonTapped() { self.count += 1 } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Now that everything is in the right place, let's run and test our app and see what we've built.
You can checkout the GitHub Repo for this project here.
In this article, we have learnt the basics of Stacks, NavigationView, Button and Property Wrappers.
I hope you guys liked this post and understood the concept of State Management.
Thanks, everyone. Have a great day.