100 Days of SwiftUI

Day 23

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 View so 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).

I’m starting to realize that a 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
This is more of a way to think about it rather than how they really work

Why does SwiftUI use “some View” for its view type?

  • some indicates an opaque return type* which in our case some View means that we’re gonna have some sort of View but we don’t want to have to tell you everything about it if you can already figure it out.
  • you don’t have to wrap views in a stack, because SwiftUI will treat them as a TupleView

Conditional modifiers

  • modifiers only apply if certain conditions are met
  • use ternary conditional operator
    • let const = condition ? true case : false case
  • you could use if statements but it may not be as efficient
    • the way he discusses it, it’s not if statements in the modifier, but in the body, e.g.

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 VStack can 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 the Text views and it will be larger
    • these are called environment modifiers
  • 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 body property
  • 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 VStack etc to containerize Views.
    • You can Group them (which means they’re shown as you specify later in code)
    • or use @ViewBuilder which mimics the existing body functionality
seems very turtles here

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
  1. create a new struct that conforms to the ViewModifier protocol
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())
  1. extend the View protocol
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.

Footnotes

  1. An opaque return type allows you to hide the exact type of a return value while still ensuring it conforms to a specific protocol. ↩︎

  2. TupleView is a generic view with a tuple as its type. This allows it to keep the information of every single subview. ↩︎

  3. mnemonic: W (what) T (true) F (false) ↩︎