100 Days of SwiftUI
2026-05-11
Project 3, part 1
Our first technique project… we’re focusing on two fundamental components of SwiftUI: views and modifiers.
Views and modifiers: Introduction
In this technique project we’re going to take a close look at views and view modifiers, and hopefully answer some of the most common questions folks have at this point – why does SwiftUI use structs for its views? Why does it use
some Viewso much? How do modifiers really work?
Why does SwiftUI use structs for views?
- an element of performance because structs are faster than classes (more compact because no inheritance)
- structs force us to isolate state
By producing views that don’t mutate over time, SwiftUI encourages us to move to a more functional design approach: our views become simple, inert things that convert data into UI, rather than intelligent things that can grow out of control.
What is behind the main SwiftUI view?
- There is nothing behind the View (that we need to worry about).
View is not equal to a Screen. A statement like Color.red is a View and where it lands in the code is how SwiftUI knows what to do with it.Why modifier order matters
- modifiers don’t change the view, they create a new view with the modifier applied
- modifiers stack, so modifier order matters (generally from top to bottom in the list)
- you can apply the same modifier more than once
Why does SwiftUI use “some View” for its view type?
Conditional modifiers
- modifiers only apply if certain conditions are met
- use ternary conditional operator
let const = condition ? true case : false case‡
- you could use
ifstatements but it may not be as efficient- the way he discusses it, it’s not
ifstatements in the modifier, but in the body, e.g.
- the way he discusses it, it’s not
Use this style
struct ContentView: View {
@State private var useRedText = false
var body: some View {
Button("Hello World") {
// flip the Boolean between true and false
useRedText.toggle()
}
.foregroundStyle(useRedText ? .red : .blue)
}
}Instead of this
struct ContentView: View {
@State private var useRedText = false
var body: some View {
if useRedText {
Button("Hello World") {
useRedText.toggle()
}
.foregroundStyle(.red)
} else {
Button("Hello World") {
useRedText.toggle()
}
.foregroundStyle(.blue)
}
}
}Environment modifiers
- things like
VStackcan be considered a “container” and modifiers on the container can affect everything inside it. For example, the.font(.title)modifier will affect all four of the text views and give them the system Title font.
VStack {
Text("Eenie")
Text("Meenie")
Text("Miney")
Text("Mo")
}
.font(.title)- some modifiers can be overridden by placing a modifier on one
View- e.g. add
.font(.largeTitle)to one of theTextviews and it will be larger - these are called environment modifiers
- e.g. add
- not all modifiers will work this way — some will stack (not override) and it just takes practice (or reading the docs) to figure out if they will stack or not
Views as properties
- “create a view as a property of your own view then use that property inside your layouts”
- can help to get code out of the
bodyproperty - can’t use a stored property that refers to another stored property
- you can create and use computed properties though
- you don’t have to use
VStacketc to containerize Views.- You can
Groupthem (which means they’re shown as you specify later in code) - or use
@ViewBuilderwhich mimics the existingbodyfunctionality
- You can
View composition
- break complex views down into smaller views (refactoring!)
From this:
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
Text("First")
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
Text("Second")
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
}
}
}To this
struct CapsuleText: View {
var text: String
var body: some View {
Text(text)
.font(.largeTitle)
.padding()
.foregroundStyle(.white)
.background(.blue)
.clipShape(.capsule)
}
}then call
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
CapsuleText(text: "First")
CapsuleText(text: "Second")
}
}
}It looks complicated but it’s easier to maintain. Also, you can override by applying a modifier in the usual way.
Custom modifiers
- you can create your own custom modifiers (say, you always style buttons in an idiosyncratic way)
- there are 2 ways to do this
- create a new struct that conforms to the
ViewModifierprotocol
struct Title: ViewModifier {
func body(content: Content) -> some View {
content
.font(.largeTitle)
.foregroundStyle(.white)
.padding()
.background(.blue)
.clipShape(.rect(cornerRadius: 10))
}
}and call it with the .modifier() modifier
Text("Hello World")
.modifier(Title())- extend the
Viewprotocol
extension View {
func titleStyle() -> some View {
modifier(Title())
}
}and call it with it’s own name
Text("Hello World")
.titleStyle()Which you choose will depend on what you’re going to do with it. Custom View modifiers can have stored properties, but extensions to View can’t.
Custom containers
This is a more advanced topic (and therefore optional).
I watched the video, but I will wait until we get to it in the normal course of business.