We’ve arrived at day 23 of the 100 Days of SwiftUI. Yesterday, we had a very extensive recap for the Guess the Flag project, which really helped me learn a lot. Today, we’re immediately diving into the third project. This project will be a technique project, which is meant to help us understand how SwiftUI works behind the scenes. There’s won’t be a lot of complex code here, but a lot of valuable examples nonetheless. Let’s dive in!
Why modifier order matters in SwiftUI
When we add a modifier to a view in SwiftUI, we actually create a new view to which the modifier is applied. This continues on for every modifier we add. That’s why the order in which modifiers are added is important. For example, let’s say you want to color the entire background of a view.
var body: some View {
VStack{
Button("Background color") {
}
.background(.indigo)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
Looking at the example above, you may expect the entire background to be colored in indigo. However, because we color the background first and only afterwards define the frame, this isn’t the case. If we switch them around though, it works just fine.
var body: some View {
VStack{
Button("Background color") {
}
.font(.largeTitle.bold())
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.indigo)
}
}
}
Why does SwiftUI use “some View” for its view type?
It’s been a little while, but we’ve talked about opaque return types before. To recap:
In a nutshell, opaque return types allow us to make our code less complex by removing the need to specify exact return types when working with protocols. For example, instead of saying that we’ll return a specific vehicle like a bike or a car, we could just say that we’ll return some vehicle. We just don’t know which one yet or we don’t want to specify it yet. Swift will be able to determine by itself what type is expected by looking at the rest of our program.
Code Maverick
As it turns out, opaque return types aren’t only used with protocols. When you see var body: some View
when starting a new SwiftUI project, it tells us that the body will return a view, we just don’t know what view yet. It could be a Text
view, a VStack
, a Button or a Color
. Swift is smart enough to figure this out in the compiler and ensures the correct view is returned.
Environment modifiers
There will be times where you want to apply the same modifiers to multiple views. An easy way to do this is using an environment modifier. For example, if you have a VStack containing multiple Text
views, you can give them all the same appearance in one go.
var body: some View {
VStack{
Text("First text view")
Text("Second text view")
Text("Third text view")
Text("Fourth text view")
}
.font(.largeTitle)
}
Conditional modifiers
It’s common to only apply a modifier once a certain condition is met. We can accomplish this with a ternary operator, which we learned about during day 5. Time sure does fly!
Just to jog our memory, a ternary operator consists of WTF. A what, the condition being checked, True, what to do if the condition being checked is true, and False, what to do if the condition is false.
struct ContentView: View {
@State private var toggleColor = false
var body: some View {
VStack{
Button("Change color") {
toggleColor.toggle()
}
.foregroundColor(toggleColor ? .indigo : .teal)
}
}
}
Views as properties
It’s possible to store views in properties and call them in our layouts. Paul explains that this allows for more complex view hierarchies.
struct ContentView: View {
let myView = Text("Hello!")
let mySecondView = Text("Goodbye!")
var body: some View {
VStack{
myView
mySecondView
}
.font(.largeTitle)
}
}
View composition
SwiftUI lets us break down big Views into smaller Views. This has multiple advantages, including a cleaner body and a better overview of our Views.
struct capsuleText: View {
var text: String
var body: some View {
Text(text)
.font(.caption)
.foregroundColor(.white)
.padding(5)
.background(.black)
.clipShape(Capsule())
}
}
struct ContentView: View {
var body: some View {
VStack {
capsuleText(text: "First")
capsuleText(text: "Second")
Text("Hello, World")
.titleStyle()
}
}
}
Custom modifiers
Last up today are custom modifiers. A custom modifier can contain multiple modifiers and apply them to a single View. They also allow for their own stored properties to be used.
struct Watermark: ViewModifier {
var text: String
func body(content: Content) -> some View {
ZStack(alignment: .bottomTrailing) {
content
Text(text)
.font(.caption)
.foregroundColor(.white)
.padding(5)
.background(.black)
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Color.blue
.frame(width: 300, height: 200)
.modifier(Watermark(text: "Code Maverick"))
}
}
}
And that’s it for day 23! Today, we wrap up this project with a review and a few challenges. Time to recharge, which is needed after todays heatwave. See you tomorrow!
100 Days of SwiftUI – Day 23 -Views and Modifiers