🌙
☀️ Dark
PART 5

SwiftUI

Declarative UI, views, modifiers, layout, state, bindings, and animation.

Intermediate 45 min read

PART 5 — SWIFTUI

1. View, Body, and Composition

Why Does This Exist?

SwiftUI was created to provide a declarative way to build user interfaces across all Apple platforms. Instead of manually updating the UI when state changes (like in UIKit), you declare what the UI should look like for a given state, and SwiftUI handles the rest.

Mental Model

Think of a View in SwiftUI not as a canvas you paint on, but as a recipe. The body property is the instructions for that recipe. When the ingredients (state) change, SwiftUI reads the recipe again to see what the new meal (UI) should look like. A View is just a lightweight value type (struct), making it extremely cheap to recreate.

Break It

swift
struct BrokenView: View {
    // Missing body property
}

This won't compile. Every View must conform to the View protocol, which requires a body property returning some View.

Debug It

If your view isn't updating, check if the state it relies on is actually marked with a property wrapper like @State or @Binding. If it's a regular property, SwiftUI won't know to observe it.

Mini Project

Create a simple profile card view combining an image, text for the name, and a text for the bio.

Real App Feature

Any screen in an app! For instance, the Settings screen in the Settings app is a composition of many smaller, reusable views.

Interview Questions

Why are Views in SwiftUI structs instead of classes?

Structs are value types and are allocated on the stack, making them extremely fast to create and destroy. Since SwiftUI constantly recreates views when state changes, using lightweight structs is crucial for performance. It also prevents shared mutable state bugs.

2. Modifiers and Layout

Why Does This Exist?

Modifiers allow you to alter the appearance or behavior of a view without changing the view itself. They chain together to build complex layouts in a readable, declarative syntax.

Mental Model

Imagine modifiers as nested boxes. When you apply a modifier to a view, you aren't changing the original view; you're wrapping it in a new view that applies the modification. Order matters! .padding().background(Color.red) is different from .background(Color.red).padding().

Break It

swift
Text("Hello")
    .background(Color.red)
    .padding()
// The padding will be outside the red background!

Debug It

Use the View Debugger in Xcode, or temporarily add .border(Color.red) to see the exact bounds of a view when debugging layout issues.

Mini Project

Build a styled button with a corner radius, specific background color, shadow, and custom padding.

Real App Feature

Customizing the look of a promotional banner with gradients, rounded corners, and drop shadows.

Interview Questions

Why does the order of modifiers matter in SwiftUI?

Because each modifier wraps the previous view in a new view. Applying padding and then a background wraps the padded view in a background. Applying a background and then padding wraps the backgrounded view in padding.

3. Stacks, Grids, Lists, and Scrolling

Why Does This Exist?

These are the fundamental container views for organizing content on the screen. They allow for vertical, horizontal, overlapping, and scrolling layouts.

Mental Model

Stacks (VStack, HStack, ZStack) are like organizing boxes on a shelf. Lists and Grids are like structured spreadsheets for repeating data. ScrollView is a window that lets you peek into a content area larger than the screen.

Break It

swift
VStack {
    ForEach(0...10000) { _ in
        Text("Too many views!")
    }
}

This will cause performance issues because VStack loads all its children immediately. For large lists, use LazyVStack or List.

Debug It

If a ScrollView isn't scrolling, check if its content actually exceeds the size of the ScrollView itself. If not, it won't scroll.

Mini Project

Build a scrollable feed of user posts using a List or LazyVStack.

Real App Feature

The Instagram feed (ScrollView + LazyVStack) or the Photos app grid (LazyVGrid).

Interview Questions

What is the difference between VStack and LazyVStack?

VStack creates and renders all of its child views immediately, even if they are off-screen. LazyVStack only creates views as they are needed (when they are about to appear on screen), making it much more memory and performance efficient for large lists.

4. Forms, Navigation, Sheets, and Alerts

Why Does This Exist?

These components map directly to standard platform UI paradigms. Forms build data entry screens, Navigation manages hierarchical flows, and Sheets/Alerts handle modal presentations.

Mental Model

A Form is a specialized List styled for user input. NavigationStack is a stack of cards you push onto or pop off of. Sheets and Alerts are temporary overlays that demand immediate attention or provide secondary workflows.

Break It

swift
NavigationLink("Go", destination: Text("Detail"))
// Used outside a NavigationStack

A NavigationLink won't work unless it's embedded somewhere within a NavigationStack.

Debug It

If a sheet isn't appearing, ensure the isPresented binding is actually being set to true. Also, ensure the sheet modifier is attached to a view that is currently on screen.

Mini Project

Build a Settings screen using a Form, with NavigationLinks to sub-settings and an Alert to confirm "Reset Settings".

Real App Feature

The iOS Settings app (Form + Navigation) or a "Share" modal (Sheet).

Interview Questions

How do you programmatically pop to the root view in a NavigationStack?

By passing a binding to the navigation path array to the NavigationStack, and clearing that array when you want to return to the root.

5. State, Bindings, Environment, and Observation

Why Does This Exist?

This is the lifeblood of SwiftUI. It's how data flows through your app and triggers UI updates. It replaces delegation and manual KVO.

Mental Model

@State is source of truth owned by a view. @Binding is a read/write reference to that truth for a child view. @Environment is a way to pass data down the tree globally without passing it through every intermediate view. @Observable (or ObservableObject) manages complex data models outside the view layer.

Break It

swift
struct MyView: View {
    var counter = 0 // Not @State
    var body: some View {
        Button("Tap") { counter += 1 } // Error: self is immutable
    }
}

Debug It

If a view isn't updating when a model changes, ensure the model is correctly using the @Observable macro (or conforming to ObservableObject with @Published properties) and the view is referencing it correctly.

Mini Project

Build a counter app where a parent view holds the state, a child view displays it, and another child view has a binding to increment it.

Real App Feature

A shopping cart where adding an item (child view) updates the total badge in the navigation bar (environment/shared observable).

Interview Questions

What is the difference between @State and @Binding?

@State creates and owns the data; it is the source of truth. @Binding does not own the data; it is merely a two-way reference to data owned elsewhere (usually an @State in a parent view).

6. Lifecycle, Identity, Animations, and Transitions

Why Does This Exist?

To control when views appear and disappear, understand how SwiftUI differentiates views, and make those changes visually pleasing.

Mental Model

onAppear/onDisappear are the lifecycle hooks. View Identity (often defined by id()) tells SwiftUI if a view is the *same* view with new data, or a entirely *new* view. Animations interpolate changes in view properties, while Transitions define how views enter or leave the hierarchy.

Break It

swift
ForEach(items, id: \.self) { item in ... }
// If items have duplicate values, SwiftUI gets confused about view identity and may crash or render incorrectly.

Debug It

If an animation isn't triggering, check if the view is being completely replaced (identity change) rather than its properties being updated. Use .id() to force or preserve identity.

Mini Project

Create a view that conditionally shows a box. Add a transition so it slides in when appearing and fades out when disappearing.

Real App Feature

The fluid animations in the Apple Music app when expanding the Now Playing sheet.

Interview Questions

How does SwiftUI determine if a View is the same as before?

SwiftUI uses Structural Identity (based on the view's type and position in the view hierarchy) and Explicit Identity (using the .id() modifier or the id parameter in a ForEach). If either changes, SwiftUI destroys the old view and creates a new one.

7. Gestures, Accessibility, Adaptive Layouts, Dark Mode, and Dynamic Type

Why Does This Exist?

To make apps usable by everyone, everywhere, under any conditions. This encompasses interactivity, inclusivity, and platform adaptation.

Mental Model

Gestures translate physical touches into intents. Accessibility ensures the UI tree makes sense to screen readers (VoiceOver). Adaptive layouts use Environment values (like Size Classes) to reshape UI. Dark Mode and Dynamic Type rely on relative colors and scalable fonts to match user preferences.

Break It

swift
Text("Label")
    .font(.system(size: 14)) // Breaks Dynamic Type by hardcoding size

Debug It

Use Xcode's Environment Overrides toggle during debugging to instantly test Dark Mode, Dynamic Type sizes, and localization without restarting the app.

Mini Project

Goal (15-20 min): Build a reusable custom interactive button using SwiftUI that tracks its own tap count using @State. It should have a scaled text label for Dynamic Type support, adapt to Dark Mode using system semantic colors, and use @Binding to pass the count to a parent view.

Sample Solution
swift
swift
struct CustomCounterButton: View {
    @Binding var totalCount: Int
    @State private var isPressed = false
    
    var body: some View {
        Button(action: {
            totalCount += 1
            let impact = UIImpactFeedbackGenerator(style: .medium)
            impact.impactOccurred()
        }) {
            VStack(spacing: 8) {
                Text("Tapped \(totalCount) times")
                    .font(.headline)
                Text("Tap me!")
                    .font(.subheadline)
            }
            .padding()
            .foregroundColor(.white)
            .background(Color.accentColor)
            .cornerRadius(10)
            .scaleEffect(isPressed ? 0.95 : 1.0)
            .animation(.easeInOut(duration: 0.1), value: isPressed)
        }
        .accessibilityLabel("Counter Button")
        .accessibilityValue("\(totalCount) taps")
    }
}

Real Application Feature

Build a production-ready Settings screen commonly found in applications like Mail or Messages. The implementation should utilize a NavigationSplitView to adapt gracefully across iPhone and iPad sizes. Implement a Form with logically grouped Sections for preferences, use @AppStorage to persist user settings, and demonstrate proper state ownership by breaking massive views down into manageable subviews.

Interview Questions

Easy: What is the difference between @State and @Binding in SwiftUI?

@State is used to declare state that is owned and managed by the view itself. @Binding is used to create a two-way connection to state that is owned by a parent view, allowing the child view to read and mutate it without owning it.

Medium: How do you ensure a SwiftUI view supports Dynamic Type?

Use relative text styles like .font(.headline) or .font(.body) instead of hardcoding point sizes. If custom sizes are absolutely required, wrap the size value using the @ScaledMetric property wrapper so it scales proportionally with the user's OS text size settings.

Hard: Why does SwiftUI use structs for Views instead of classes like UIKit?

Structs are value types, which are extremely fast to allocate and destroy compared to heap-allocated objects. Because SwiftUI's declarative model frequently recreates the view tree during state changes, using lightweight structs avoids the overhead of reference counting and prevents state synchronization issues typical of mutable shared objects. The struct is merely a lightweight description of the UI, while SwiftUI manages the persistent rendering tree underneath.

Senior: Explain view identity in SwiftUI and its impact on state preservation and animations.

SwiftUI tracks views through either structural identity (their position in the view hierarchy code) or explicit identity (using the .id() modifier). If a view's identity remains constant across an update, SwiftUI will merely update its properties, animate any changes, and preserve its internal @State. If the view's identity changes, SwiftUI treats it as a completely new view, destroying the old one (losing its @State) and abruptly drawing the new one without an animation transition.

Mini Project (20-30 min)

Create a custom ViewModifier in SwiftUI to standardize a stylistic card appearance and apply it seamlessly.

View Solution
swift

// Reusable Custom Modifier
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}
extension View {
    func cardStyle() -> some View {
        self.modifier(CardModifier())
    }
}
                

Bigger Project (1-2 hours)

Implement an interactive, state-driven list using SwiftUI that supports deletions and integrates navigation.

View Solution
swift

// Interactive State-driven List
struct ContentView: View {
    @State private var items = ["Item 1", "Item 2", "Item 3"]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
                .onDelete { indexSet in
                    items.remove(atOffsets: indexSet)
                }
            }
            .navigationTitle("My List")
            .toolbar { EditButton() }
        }
    }
}
                

Interview Questions

1. (Easy) What is the difference between `@State` and `@Binding`?
View Answer
`@State` creates and owns a source of truth for a value directly within a View. `@Binding` creates a reference to state owned by another View, allowing for two-way communication to read and modify that value safely.
2. (Medium) How does SwiftUI's view identity affect animations?
View Answer
SwiftUI uses structural identity (the view's hierarchy placement) or explicit identity (the `id` modifier) to track views. Animations apply changes smoothly only when SwiftUI identifies that the same view transitioned to a new state rather than being destroyed and replaced.
3. (Hard) Explain the view update cycle when an `@EnvironmentObject` changes.
View Answer
When a property on an `ObservableObject` published via `@EnvironmentObject` updates, the `objectWillChange` publisher fires. SwiftUI invalidates the rendering tree for any views actively depending on this object, recalculates their `body`, and diffs the old and new structural output to apply UI updates efficiently.