Open menu with table of contents Swift and SwiftUI: A Different Way of Thinking
Logo of Stuttgart Media University for light theme Logo of Stuttgart Media University for dark theme
Mobile Application Development 2

Swift and SwiftUI: A Different Way of Thinking

Stuttgart Media University

1 Today's Goal

  • Small Recap of last Session
  • Swift Enums, Switch cases, and Functions: The Swifty Way
  • Deconstructing a SwiftUI View

2 Block 1: Modeling State Safely

2.1 The Problem

You're building a social media app. You need to model a UserProfile.

Requirements:

  • username: A String
  • followerCount: An Int
  • tier: The user's subscription level

The tiers have different data requirements:

  • Free: No additional data
  • Premium: May have an optional promotional code (String?) they used to sign up
  • Pro: Must have a subscription expiration date (Date)

The challenge: Your model should make it impossible to represent an invalid state. A free user cannot have an expiration date. A pro user must have one.

2.2 Your Task (10 Minutes)

Model this UserProfile and its tier.

Use a Swift enum for the tier—we expect that from your Java background.

Write the code. Think about how you'll prevent invalid states.

Note: Swift properties are public by default. You do not need to write explicit setters/getters like in Java.

Begin.

3 The Common Solution (And Its Problem)

Most of you likely wrote something like this:

enum UserTier {
    case free, premium, pro
}

struct UserProfile {
    let username: String
    let tier: UserTier
    var proExpirationDate: Date?
    var premiumPromoCode: String?
}

This works, but it has a critical flaw: it allows invalid states.

Nothing prevents this: UserProfile(username: "test", tier: .free, proExpirationDate: Date())

Your model can lie. Optional properties that should only exist for certain cases create a validation burden throughout your entire codebase.

3.1 The Swift Approach: Enums with Associated Values

Swift's enums can carry data specific to each case. This allows you to model the relationship between state and its data directly.

enum UserTier {
    case free
    case premium(promoCode: String?)
    case pro(expiresOn: Date)
}

struct UserProfile {
    let username: String
    let tier: UserTier
}

Now it's impossible to create a user with mismatched data. The compiler enforces your business rules.

This is what we mean by "type safety"—using the type system to prevent entire categories of bugs.

3.2 Working with Associated Values: switch

Swift's switch is exhaustive—you must handle every case. Combined with pattern matching, you can safely extract associated values.

func describeTier(for profile: UserProfile) {
    switch profile.tier {
    case .free:
        print("\(profile.username) is a Free user")
    case .premium(let promoCode):
        let code = promoCode ?? "none"
        print("\(profile.username) is Premium (code: \(code))")
    case .pro(let expirationDate):
        print("\(profile.username) is Pro, expires \(expirationDate)")
    }
}

If you add a new tier, this code won't compile until you handle it. The compiler becomes your safety net.

3.3 Enum Power-ups: Raw Values and Computed Properties

Associated values are just one feature. Enums can also have raw values for simple cases and contain their own logic.

enum UploadError: String, Error {
    case tooLarge = "Upload exceeds file size limit."
    case unsupportedFormat = "File format is not supported."
    case networkFailed = "The network connection was lost."
}

// You can create an enum from its raw value
let error = UploadError(rawValue: "Upload exceeds file size limit.") // .tooLarge

// And because it conforms to `Error`, you can `throw` it.

An enum is not just a dumb data type. It can have functions and computed properties. This is often a better place for logic than a global function.

3.4 Your Task 2 (10 Minutes)

Add a computed property to the UserTier enum called isActivePro.

It should return true only if the tier is .pro AND its expiration date is in the future. Otherwise, it should return false.

// Add this inside your UserTier enum
var isActivePro: Bool {
    // Your implementation here
}
// You can check for parameters within an if with
case .someCase(let someParameter) = Seft // 'Self' needs to be used it's accessing it's own information within a computed property

3.5 Solution: guard vs. if

Here are two ways to solve that task.

The if approach (works, but is nested):

var isActivePro: Bool {
    if case .pro(let expirationDate) = self {
        if expirationDate > Date() {
            return true
        }
    }
    return false
}

The guard approach (cleaner, flatter):

var isActivePro: Bool {
    guard case .pro(let expirationDate) = self, expirationDate > Date() else {
        return false
    }
    return true
}

guard is perfect for checking multiple conditions and exiting early if any of them fail. This makes the "happy path" (the successful return) clear and un-nested.

3.6 A Word on Functions: Clarity and Intent

Swift's function syntax is designed to make call sites read like natural English sentences. This is a major philosophical difference from Java/C.

Look at the call sites, not the function definitions:

// Confusing: What is "Mike"? A name? An ID?
printUserDetails("Mike") 

// Clear: The argument label explains the value.
printUserDetails(for: "Mike")

The syntax that enables this:

// func <name>(<external name> <internal name>: <Type>)
func printUserDetails(for username: String) {
    // inside the function, you use 'username'
    print("User: \(username)")
}

// Use _ to have no external name
func printUserDetails(_ username: String) {
    print("User: \(username)")
}

The goal is always readability at the point of use.

// This reads like a sentence: "Get item for user 'Michael-525' from 'server' with retries 3"
getItem(for: "Michael-525", from: "server", withRetries: 3)

3.7 Block 1 Knowledge Check

Instead of a summary, let's test your understanding.

  1. What problem do associated values solve that raw values cannot?
  2. What does it mean for a switch statement to be exhaustive, and why is that a good thing?
  3. Explain a scenario where guard is a better choice than a simple if statement.
  4. What is the main goal of Swift's argument label system for functions?

4 Block 2: Understanding SwiftUI


4.1 The Starting Point

Open a new SwiftUI project. Look at ContentView.swift.

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

This code applies the Swift concepts we just covered. Your task is to understand why it's written this way.

4.2 The Investigation

This isn't magic. It's the deliberate application of Swift's type system.

You'll be assigned a specific part of this code. Your job is to research it and explain the language concepts behind it.

Timeline:

  • 30 minutes: Research and experimentation in your groups
  • ~5 minutes per group: Live presentations in Xcode

Refer to the next slides for your group's specific task.

5 Group Assignments

5.1 Instructions

  • Work in your assigned groups.
  • You have 30 minutes to research and prepare your presentation.
  • Base your explanations on Swift language concepts, not just "what it does".
  • Present live in Xcode. No slides. Show, don't just tell.
  • At least one person presents, but the whole group should be prepared and stand in front.

5.2 Research Resources

  • Xcode's Quick Help (Option + Click)
  • Official Swift documentation on docs.swift.org
  • SwiftUI documentation on developer.apple.com
  • Experiment in a playground or new SwiftUI preview

Don't just read articles about SwiftUI. Run experiments. Change code. See what breaks. That's how you learn.

You are in charge of knowlege Transfer to the others. Do your best. You can do this!

5.3 Presentation Format

Structure your live demonstration:

  1. The Question: Restate what you were investigating.
  2. The Evidence: Show what you found (code, documentation, experiments) in Xcode.
  3. The Explanation: Explain the underlying Swift concept.
  4. The Conclusion: Why is it designed this way? What problem does it solve?

6 Group 1: The View Declaration

6.1 Your Focus: struct ContentView: View

6.2 Investigation Questions:

  1. The Experiment: Change struct to class. What happens? Document the compiler errors exactly.

  2. The Type Question: Why does SwiftUI use struct instead of class?

    💡 Hint: Create a simple struct vs. class outside SwiftUI. What happens when you assign one to another variable and modify it? How does copying behavior differ?

  3. The Architecture Question: SwiftUI views are lightweight, disposable descriptions of UI. How does using struct support this design? What would happen if views were reference types that get shared?

  4. The Identity Problem: Value types don't have identity - two structs with the same data are considered equal. Reference types have identity - two class instances are different even with the same data. Why is "no identity" actually good for SwiftUI views?

  5. Your Conclusion: Provide a technical explanation for why SwiftUI requires views to be value types.

  6. Optional Performance Question: SwiftUI creates and destroys views constantly. Research: Are structs stored on the stack or heap? Classes? Why does this matter for performance?

7 Group A: The Body Property

7.1 Your Focus: var body: some View

7.2 Investigation Questions:

  1. Computed Property: body is a computed property. It calculates its value when asked; it doesn't store one. Why is this essential for a system where views are constantly being re-created?

  2. The Protocol Requirement: View is a protocol. What is the only requirement of this protocol?

    💡 Hint: Use Xcode's "Jump to Definition" (Cmd+Click on View).

  3. The some Keyword: This declares an "opaque type". Research what this means.

    💡 Hint: Try replacing some View with the explicit type Xcode suggests (Option+Click on body). Look at how complex it gets: VStack<TupleView<(ModifiedContent<Image, _ImageModifierTransform>, Text)>>. Why is forcing you to write this problematic?

  4. Your Conclusion: Explain why body is a computed property returning an opaque type some View. What problems does this solve?

8 Group (Greek Alpha): The Layout Container

8.1 Your Focus: VStack { ... }

8.2 Investigation Questions:

  1. What is VStack? Use Xcode to determine its type (struct, class, etc.). Jump to its definition.

  2. The Trailing Closure: The {...} syntax is a trailing closure. Why can you write VStack { } instead of VStack(content: { })?

    💡 Hint: This is a Swift language feature, not SwiftUI-specific. Look up "trailing closure syntax".

  3. Multiple Children: How can VStack accept multiple views (Image AND Text) when it's just a closure that should return one thing?

    💡 Experiment: Jump to VStack's definition. Look at the initializer's closure parameter. What attribute do you see marked on it? Research @ViewBuilder.

  4. Layout Behavior: Try different numbers of views. Try HStack instead of VStack. What does VStack actually do with its children?

  5. Your Conclusion: Explain that VStack is a container view that uses Swift's result builder feature (via @ViewBuilder) to let you write multiple views in a clean, readable way, then arranges them vertically.

9 Group (Roman I): View Modifiers & Immutability

9.1 Your Focus: .imageScale(.large), .foregroundStyle(.tint), .padding()

9.2 Investigation Questions:

  1. What are modifiers? Pick one modifier (like .padding()). Option+Click on it in Xcode. What type does it return?

    💡 Hint: Notice it doesn't return VStack or Image - it returns something like ModifiedContent<...>.

  2. The Wrapping Pattern: Modifiers don't change the original view. They wrap it in a new view. Why is this the case?

    💡 Experiment: Create a Text view, store it in a variable, apply .padding(), and store that in another variable. Print their types using type(of:). Are they the same type? What does this tell you?

    Note: You can write 'normal' code in every swiftUI class. print() will work as well. type(of:) see: https://developer.apple.com/documentation/swift/type(of:)

  3. Method Chaining: You can write .padding().background(.blue).border(.red). Based on #1 and #2, explain why this works. What does each modifier return that allows the next modifier to be called?

    💡 Hint: Each modifier returns something that also conforms to View.

  4. Your Conclusion: Explain that views are immutable data structures, and modifiers create new views by wrapping old ones. Why is this important for a system where views are constantly recreated? How does this relate to struct being a value type?

9.3 Bonus Investigation (if time):

Compare Image(systemName:) and Text() initializers. They're both structs that conform to View. What makes them "primitive" views versus container views like VStack?