Skip to main content

    Lesson 6 • Intermediate

    SwiftUI Basics 📱

    By the end of this lesson you'll be able to build a real iOS screen by hand: describe it with a View, stack and style the pieces with modifiers, add a tappable button, and make the screen update itself with @State.

    What You'll Learn in This Lesson

    • • Write a View struct with a body that describes the screen
    • • Show content with Text and Image (SF Symbols)
    • • Arrange views using VStack, HStack, and ZStack
    • • Style anything by chaining modifiers like .font, .padding, .foregroundColor
    • • Add a Button that runs an action when tapped
    • • Make the UI react automatically to data with @State

    🧠 Declarative vs Imperative

    In the old imperative style you wrote every step: create a label, set its text, add it to the view, then later find that label and change it by hand each time the data moved. That's a lot of bookkeeping, and bugs creep in when the screen and the data drift out of sync.

    SwiftUI is declarative: you write one body that says what the screen looks like for the current state. When the state changes, SwiftUI re-runs body and updates only what actually changed. You describe the what; the framework handles the how.

    1️⃣ Your First View: Text, Image & Modifiers

    Everything you see in SwiftUI is a View — a lightweight struct that describes something on screen. Every view struct has a body that returns the content. You customise a view by chaining modifiers: methods like .font(), .foregroundColor(), and .padding() that each return a new, tweaked view. Read this worked example, then build it.

    Worked example: a styled welcome screen
    import SwiftUI
    
    // Every screen is a struct that conforms to the View protocol.
    // The 'body' property describes WHAT to show — SwiftUI builds it.
    struct WelcomeView: View {
        var body: some View {
            // A vertical stack places its children top-to-bottom.
            VStack {
                // An SF Symbol icon (Apple ships thousands for free).
                Image(systemName: "swift")
                    .font(.system(size: 60))   // make the symbol big
                    .foregroundColor(.orange)  // tint it orange
    
                // A line of text, customised by chaining modifiers.
                Text("Hello, SwiftUI!")
                    .font(.largeTitle)         // big title text style
                    .bold()                    // make it bold
                    .foregroundColor(.blue)    // colour the text blue
            }
            .padding()                         // breathing room around the stack
        }
    }
    Output
    On screen:  a centred column showing a large orange Swift
    logo, and directly below it the words "Hello, SwiftUI!" in
    big, bold, blue title text — with empty space (padding)
    around the whole group.
    SwiftUI draws a real UI, so there's no console output — the panel above describes what renders. Paste this into an Xcode project and open the Canvas preview to see it.

    2️⃣ Layout with Stacks

    You arrange views with three stacks. VStack lays children out top-to-bottom, HStack left-to-right, and ZStack layers them back-to-front. You nest stacks inside each other to build any layout — no manual constraints required. The spacing: argument controls the gap between children.

    Worked example: a profile card built from stacks
    import SwiftUI
    
    // Three stacks arrange views; you nest them to build any layout:
    //   VStack — top to bottom     HStack — left to right
    //   ZStack — back to front (layered)
    struct ProfileCard: View {
        var body: some View {
            VStack(spacing: 12) {                  // vertical, 12pt between rows
                Image(systemName: "person.circle.fill")
                    .font(.system(size: 70))
                    .foregroundColor(.blue)
    
                Text("Alice Johnson")
                    .font(.title2)
                    .bold()
    
                // A horizontal row of two labels, side by side.
                HStack(spacing: 30) {              // left to right
                    Text("142 Posts")
                    Text("1.2k Followers")
                }
                .font(.subheadline)
                .foregroundColor(.gray)
    
                // ZStack stacks views on top of each other (back to front).
                ZStack {
                    Color.blue                     // back layer: a blue rectangle
                    Text("iOS Developer")          // front layer: text on top
                        .foregroundColor(.white)
                        .padding(8)
                }
                .cornerRadius(8)
            }
            .padding()
        }
    }
    Output
    On screen:  a centred card. From top to bottom — a blue
    person icon, the bold name "Alice Johnson", then a single
    row with "142 Posts" and "1.2k Followers" side by side in
    small grey text, and finally a rounded blue banner with the
    white words "iOS Developer" sitting on top of it.
    The panel above describes the rendered card. Build it in Xcode and open the Canvas preview to see the layout.

    Your turn. The view below is almost finished — fill in the three blanks marked ___ using the hints, then preview it.

    🎯 Your turn: style a line of text
    import SwiftUI
    
    struct GreetingView: View {
        var body: some View {
            // 🎯 YOUR TURN — replace each ___ then preview it in Xcode.
    
            Text("Good morning!")
                // 1) Make the text use the large-title style
                .font(___)                  // 👉 .largeTitle
                // 2) Colour the text green
                .foregroundColor(___)       // 👉 .green
                // 3) Add space around the text so it isn't cramped
                .___()                      // 👉 padding
    
            // ✅ Expected on screen:
            //    large green "Good morning!" text with padding around it.
        }
    }
    Replace each ___ with the value in its // 👉 hint, then check the result against the "Expected on screen" comment in an Xcode preview.

    3️⃣ Buttons & @State: a Reactive Counter

    A Button takes a title and an action — a closure (a block of code in { }) that runs on tap. To make the screen respond, you store data in a property marked @State. A SwiftUI view is a struct that gets rebuilt constantly, so @State tells SwiftUI to keep that value safe between rebuilds and to re-run body automatically whenever it changes. That automatic refresh is what "reactive" means.

    Worked example: a tap counter
    import SwiftUI
    
    // @State holds a value that BELONGS to this view. When it changes,
    // SwiftUI automatically re-runs body and redraws the screen.
    struct CounterView: View {
        @State private var count = 0          // start at 0, owned by the view
    
        var body: some View {
            VStack(spacing: 20) {
                // \(count) drops the current value into the text.
                Text("Count: \(count)")
                    .font(.largeTitle)
    
                // A Button takes a title and an action closure { ... }.
                Button("Tap me") {
                    count += 1                // change state -> view refreshes
                }
                .buttonStyle(.borderedProminent)   // a filled, tappable button
            }
            .padding()
        }
    }
    Output
    On screen:  "Count: 0" in large text with a filled "Tap me"
    button under it. Each tap increases the number — tap three
    times and the label reads "Count: 3". You never told the
    label to update; changing @State did it for you.
    The panel above describes the live, tappable result. Run it in Xcode (Canvas preview) and tap the button to watch the count change.

    Now wire up your own state. Fill in the two blanks so the heart toggles between empty and filled each time the button is tapped:

    🎯 Your turn: a like button with @State
    import SwiftUI
    
    struct LikeButton: View {
        // 1) Declare a @State Bool that starts as false
        @State private var liked = ___        // 👉 false
    
        var body: some View {
            VStack(spacing: 16) {
                Image(systemName: liked ? "heart.fill" : "heart")
                    .font(.largeTitle)
                    .foregroundColor(.red)
    
                Button("Toggle like") {
                    // 2) Flip the value so the heart fills / empties
                    liked = ___                // 👉 !liked
                }
            }
            // ✅ Expected on screen: an outline heart that becomes a
            //    filled red heart (and back) each time you tap the button.
        }
    }
    Fill in the ___ blanks using the // 👉 hints, then tap the button in an Xcode preview and confirm the heart fills and empties.

    Common Errors (and the fix)

    • "Cannot assign to property: 'self' is immutable" — you tried to change a plain var from inside body. A view is a struct, so its stored values can't be mutated directly. Mark the property @State private var and SwiftUI will let you change it (and redraw the view).
    • Modifier order changes the result. .padding().background(.blue) pads first then colours the larger area (a blue border); .background(.blue).padding() colours first then adds clear space. When a layout looks off, swap two modifiers.
    • "Type 'MyView' does not conform to protocol 'View'" — you forgot : View on the struct, or body doesn't return a view. Declare struct MyView: View and make body return exactly one view.
    • "Function declares an opaque return type 'some View'…" — your body returns two or more views at the top level. Wrap them in a VStack (or other stack) so it returns a single view.

    Pro Tips

    • 💡 Use the Canvas preview (⌥⌘P) to see changes instantly without building and running.
    • 💡 Mark @State as private — that state belongs to this view alone; other views shouldn't reach in.
    • 💡 Browse SF Symbols in Apple's free SF Symbols app to find icon names for Image(systemName:).

    📋 Quick Reference — SwiftUI

    PieceUsage
    TextText("Hi").font(.title)
    ImageImage(systemName: "star.fill")
    VStackVStack { Text("A"); Text("B") }
    HStackHStack { ... } (side by side)
    ZStackZStack { ... } (layered)
    ButtonButton("Tap") { count += 1 }
    @State@State private var count = 0
    padding.padding() / .padding(16)

    Frequently Asked Questions

    Q: What is the difference between declarative and imperative UI?

    Imperative UI (like older UIKit) means you write step-by-step instructions to create a label, set its text, add it to the screen, and then update it by hand whenever data changes. Declarative UI (SwiftUI) means you describe what the screen should look like for any given state, and the framework figures out the steps and the updates for you. You write what, not how.

    Q: Why does my view say it must conform to 'some View'?

    Every SwiftUI view is a struct that adopts the View protocol, and that protocol requires a body property of type 'some View'. If you forget ': View' on the struct, or your body returns nothing, the compiler complains. The fix is to declare 'struct MyView: View' and make sure body returns exactly one view (wrap multiple views in a stack).

    Q: Why can't I just change a normal variable to update the screen?

    A SwiftUI view is a value type (a struct) that gets thrown away and rebuilt constantly, so a plain 'var' would lose its value on every redraw and changing it would not trigger anything. Marking a property with @State tells SwiftUI to store that value outside the struct and re-run body whenever it changes — that is what makes the UI reactive.

    Q: Does the order of modifiers actually matter?

    Yes, almost always. Modifiers wrap the view from the inside out, so .padding().background(.blue) adds space first and then colours the larger area (you get a blue border), whereas .background(.blue).padding() colours the view first and then adds clear space around it. When a layout looks wrong, try swapping two modifiers.

    Q: Do I need a Mac and Xcode to follow along?

    SwiftUI renders to a real iOS/macOS screen, so the proper tool is Xcode on a Mac, where the Canvas preview shows your view live as you type. If you only want to experiment with the Swift code itself you can use an online playground, but to actually see the interface you will want Xcode's preview.

    Mini-Challenge: Quantity Stepper

    No blanks this time — just a brief and an outline. Build a small view that shows a number with a minus button on its left and a plus button on its right, where tapping each updates the number live. You'll combine a VStack, an HStack, two Buttons, and one @State value.

    🎯 Mini-Challenge: build a stepper
    import SwiftUI
    
    struct StepperCard: View {
        // 🎯 MINI-CHALLENGE: a quantity picker
        // 1. Add a @State Int called "quantity" starting at 1.
        // 2. In a VStack, show Text("Quantity: \(quantity)") in a title font.
        // 3. Add an HStack with two buttons: "−" and "+".
        //    - "+" does  quantity += 1
        //    - "−" does  quantity -= 1   (don't worry about going below 0 yet)
        //
        // ✅ Expected on screen: a number with a − button on its left and a
        //    + button on its right; tapping each changes the number live.
    
        var body: some View {
            // your code here
            Text("Replace me")
        }
    }
    Follow the numbered comments, replace the placeholder body, and preview it in Xcode. Success looks like the "Expected on screen" comment.

    🎉 Lesson Complete!

    • ✅ A screen is a View struct whose body describes the content
    • Text and Image show content; modifiers like .font, .padding, .foregroundColor style it
    • VStack, HStack, and ZStack arrange views — nest them for any layout
    • ✅ A Button runs an action closure when tapped
    • @State makes the UI reactive: change the value and SwiftUI redraws automatically
    • ✅ SwiftUI is declarative — you describe the result, not the steps
    • Next lesson: Working with APIs — fetch real data and show it in your app

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service