Skip to content
beginner Phase 2 · SwiftUI Fundamentals

Introduction to SwiftUI

Understand declarative UI, SwiftUI's design philosophy, and how it compares to UIKit.

35m
0 problems
Topic Progress 0%

Declarative vs Imperative UI

The Problem with Imperative UI

With UIKit, you imperatively tell the system how to build and update your interface. You create views, configure properties, add subviews, define constraints, and manually update state when things change. This creates a growing gap between what the UI looks like and the code that manages it.

// UIKit - Imperative approach
let label = UILabel()
label.text = "Hello, World!"
label.textColor = .blue
label.font = .systemFont(ofSize: 18)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
    label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

// Later, to update:
label.text = "Updated text"
label.textColor = .red

Every time the UI needs to change, you manually find the view and mutate its properties. Forgetting one update creates visual bugs. As screens grow, this imperative bookkeeping becomes a significant source of errors.

The Declarative Approach

SwiftUI flips the model. You write a function that describes what the UI should look like for a given state. When the state changes, SwiftUI automatically recomputes the view and updates the display.

// SwiftUI - Declarative approach
struct ContentView: View {
    @State private var isRed = false
    
    var body: some View {
        Text("Hello, World!")
            .font(.system(size: 18))
            .foregroundColor(isRed ? .red : .blue)
            .onTapGesture { isRed.toggle() }
    }
}

You describe the desired state, not the steps to reach it. SwiftUI figures out the most efficient way to update the screen.

Why Declarative Matters

Declarative code is easier to reason about because the UI is a pure function of its state. Given the same state, you always get the same UI. This eliminates entire categories of bugs related to inconsistent view state, forgotten updates, and stale references.

Mental Model

Think of SwiftUI views as recipes, not constructions. A recipe describes ingredients and steps; it does not build the dish itself. Similarly, a SwiftUI view describes what the screen should look like, and the SwiftUI runtime handles the actual rendering and updates.

Aspect UIKit (Imperative) SwiftUI (Declarative)
UI Definition Code + Interface Builder Swift code only
State Updates Manual find & mutate Automatic recomputation
Layout Auto Layout constraints Stacks, Grids, Frames
Previews None built-in Xcode Previews
Learning Curve Steep Gentle for beginners

SwiftUI Architecture

The View Protocol

In SwiftUI, every view is a struct conforming to the View protocol. The protocol requires a single property: body, which returns some View. The body property describes the view's content and layout.

struct GreetingView: View {
    var name: String
    
    var body: some View {
        VStack {
            Text("Hello")
            Text(name)
                .font(.largeTitle)
        }
    }
}

Because views are structs, they are value types. This means SwiftUI can freely copy, compare, and discard them without worrying about reference semantics or memory management.

The View Graph

When SwiftUI encounters a view hierarchy, it builds a view graph -- a tree of view nodes that represent your interface. The graph is rebuilt when state changes, but SwiftUI is smart about which parts need updating.

struct ParentView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            ChildView()
            Button("Increment") { count += 1 }
        }
    }
}

struct ChildView: View {
    var body: some View {
        Text("I am a child")
    }
}

When count changes, SwiftUI re-evaluates ParentView.body and determines that ChildView has not changed. It skips re-evaluating ChildView.body entirely, saving work.

Diffing and Updates

SwiftUI uses a diffing algorithm similar to a virtual DOM. When the view graph is re-evaluated, SwiftUI compares the new tree with the old one. It identifies insertions, deletions, and modifications, then issues the minimal set of updates to the underlying rendering system.

This diffing happens at the view boundary level. If a parent view's body changes but a child view's inputs are the same, the child is skipped. This is why struct-based views with stable identity are important for performance.

The Rendering Pipeline

  1. State change: A @State, @Binding, or @Observable value changes.
  2. Invalidation: SwiftUI marks the affected view as needing an update.
  3. Re-evaluation: The view's body property is re-computed.
  4. Diffing: The new output is compared with the previous output.
  5. Update: Only the changed parts of the underlying AppKit/UIKit layer are modified.

This pipeline is fast because SwiftUI optimizes each step. Views are structs (cheap to create), the diffing is targeted (only changed branches are compared), and the rendering uses platform-native primitives.

Identity and Stability

SwiftUI tracks views by their identity in the hierarchy. When views are in loops or conditionals, SwiftUI uses stable identifiers to match old and new views. This is why ForEach requires an id parameter:

ForEach(items, id: \.id) { item in
    Text(item.name)
}

Without stable identity, SwiftUI cannot correctly match views across updates, leading to unexpected state loss or visual glitches.

SwiftUI vs UIKit

Coexistence, Not Replacement

SwiftUI does not replace UIKit -- it builds on top of it. Under the hood, SwiftUI views ultimately render through UIKit (on iOS) or AppKit (on macOS). This means you can mix SwiftUI and UIKit in the same project, which is essential for gradual adoption.

// Using UIKit inside SwiftUI
struct WebViewWrapper: UIViewRepresentable {
    let url: URL
    
    func makeUIView(context: Context) -> WKWebView {
        WKWebView()
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.load(URLRequest(url: url))
    }
}

// Using SwiftUI inside UIKit
let hostingController = UIHostingController(rootView: MySwiftUIView())

When to Choose SwiftUI

SwiftUI excels for:

  • New projects where you want a clean, modern architecture
  • Simple to moderate UIs that map well to SwiftUI's component library
  • Prototyping where rapid iteration with Xcode Previews is valuable
  • Cross-platform apps targeting iOS, macOS, watchOS, and tvOS

When to Choose UIKit

UIKit remains strong for:

  • Complex, highly custom UIs that push beyond SwiftUI's built-in components
  • Performance-critical rendering with fine-grained control over the rendering pipeline
  • Existing codebases with substantial UIKit investment
  • Advanced interactions like custom gesture recognizers or complex animation choreography

Feature Comparison

Feature SwiftUI UIKit
Declarative syntax Yes No
Previews Built-in Via third-party
Animations Simple, integrated Complex, manual
Accessibility Automatic Manual setup
Testability Excellent (structs) Moderate
Ecosystem maturity Growing Very mature
Platform support iOS 13+, macOS 10.15+ All versions

Migration Strategy

Most teams adopt SwiftUI incrementally. Start with new screens or feature flags, wrap existing UIKit views in UIViewRepresentable, and gradually convert screens as confidence grows. The @main entry point can be a SwiftUI App struct that hosts UIKit view controllers through UIViewControllerRepresentable.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            MainTabView()
        }
    }
}

This hybrid approach lets you benefit from SwiftUI's ergonomics while preserving your investment in battle-tested UIKit code.

Quiz

1. What is the primary difference between SwiftUI and UIKit?

Question 1 options

2. Why are SwiftUI views defined as structs instead of classes?

Question 2 options

3. Can SwiftUI and UIKit be used in the same project?

Question 3 options

4. What is the role of the `body` property in a SwiftUI view?

Question 4 options

5. What happens during SwiftUI diffing?

Question 5 options

Flashcards

Question

What is declarative UI?

Answer

A paradigm where you describe what the UI should look like for a given state, rather than imperatively telling the system how to build and update it.

Question

What protocol must every SwiftUI view conform to?

Answer

The View protocol, which requires a body property returning some View.

Question

Why are SwiftUI views value types (structs)?

Answer

Value types allow SwiftUI to cheaply copy, compare, and discard views for efficient diffing and view graph management.

Question

What wraps UIKit views in SwiftUI?

Answer

UIViewRepresentable protocol, which bridges UIKit views into the SwiftUI view hierarchy.

Question

What is the SwiftUI rendering pipeline?

Answer

State change -> View invalidation -> Body re-evaluation -> Diffing -> Targeted UI updates.

Revision Notes

Key Takeaways

  • 1. Declarative UI describes what the UI should look like, not how to build it
  • 2. SwiftUI views are structs with a body property returning some View
  • 3. SwiftUI automatically diffs and updates only changed parts of the UI
  • 4. SwiftUI and UIKit can coexist through bridging protocols
  • 5. Understanding the view graph and diffing is key to writing performant SwiftUI

Interview Tips

  • Be ready to explain the difference between declarative and imperative UI paradigms
  • Know why SwiftUI uses structs (value semantics) instead of classes (reference semantics)
  • Understand the view graph lifecycle: state change -> invalidation -> re-evaluation -> diffing -> update
  • Be prepared to discuss when you would choose SwiftUI vs UIKit for a given project

Cheat Sheet

SwiftUI is declarative: describe UI as a function of state.

Every view is a struct conforming to View protocol with a body property.

SwiftUI builds a view graph, diffs old vs new, and applies minimal updates.

Coexist with UIKit via UIViewRepresentable and UIHostingController.

Views are value types for efficient copying and comparison.