Back to Topics
Term 1 DI10-1, DI10-2, DSN10-1

About Me Swift Playground

Build a real 5-tab iOS app in Swift Playgrounds on your iPad

Learning Objectives

Understand the SwiftUI app lifecycle: @main, App, WindowGroup, ContentView
Build a multi-tab interface using TabView, tabItem, and Label
Use VStack, HStack, and ZStack to compose layouts
Apply modifiers to customise appearance and behaviour of views
Clip and frame images using clipShape, resizable, and overlay
Apply custom fonts via a FontNames struct
Use SF Symbols with Image(systemName:)
Specify colours using RGB, HSB, and greyscale models
Create scrollable content with ScrollView (vertical and horizontal)
Use Group to work around the 10-view limit in stacks
Create collapsible UI sections with DisclosureGroup
Declare and use arrays of String values
Use randomElement() and the ?? nil-coalescing operator
Store and update reactive state with @State and Button
Build a fifth custom tab (YourTab) from scratch
Configure app name, icon, and accent colour in App Settings

What You're Building

The About Me app is a real, running iOS app — not a prototype or a mock-up. It lives on your iPad, opens like any other app, and has five tabs you can swipe between. Each tab teaches a new SwiftUI concept while you personalise it to be all about you.

Home
Photo, name, HStack, custom fonts, background
Story
ScrollView, Group, Divider, inline images
Favorites
Horizontal ScrollView, DisclosureGroup, Color
Fun Facts
@State, Button, arrays, randomElement()
Your Tab
Blank canvas — you design it from scratch

Why Swift? Swift is Apple's programming language — the same one used to build every app on the App Store. Learning SwiftUI here means you're using the same tools as professional iOS developers at companies worldwide.

1. The App Entry Point

Every SwiftUI app starts with a single file marked @main. This tells the system: "start here." In AboutMeApp.swift you'll see the simplest possible app shell:

import SwiftUI

@main
struct AboutMeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
@main

Marks this struct as the program entry point. There can only be one in a project.

App protocol

Every SwiftUI app conforms to the App protocol, which requires a body of type Scene.

WindowGroup

The standard scene for apps with a main window. It launches ContentView as the first visible screen.

You'll rarely change this file. Its job is just to hand control to ContentView, which is where the real app lives.

2. TabView — The App's Navigation Skeleton

ContentView.swift uses a TabView to create the tab bar at the bottom of the screen. Each child view inside TabView becomes one tab.

TabView {
    HomeView()
        .tabItem {
            Label("Home", systemImage: "person")
        }

    StoryView()
        .tabItem {
            Label("Story", systemImage: "book")
        }

    FavoritesView()
        .tabItem {
            Label("Favorites", systemImage: "star")
        }

    FunFactsView()
        .tabItem {
            Label("Fun Facts", systemImage: "hand.thumbsup")
        }

    YourTab()
        .tabItem {
            Label("My Tab", systemImage: "heart.fill")
        }
}

How a Tab Works

Each tab has two parts:

1. The View

The full-screen content shown when the tab is active — e.g. HomeView(), StoryView().

2. The .tabItem modifier

Sets the icon and label shown in the tab bar. Uses a Label with a title string and an SF Symbol name.

TabItem rule

The .tabItem modifier only accepts Label, Text, and Image views. Using a Button or any other view type will produce an empty tab — it won't error, it just silently disappears.

3. Views: The Building Blocks

In SwiftUI, everything you see is a View. Text is a view. An image is a view. A button, a colour block, a divider — all views. You compose complex screens by nesting simple views inside container views.

Every custom view is a struct that conforms to the View protocol. That means it must have a body property that returns some View:

struct HomeView: View {

var body: some View {

// Everything inside here is what gets displayed

Text("All About")

}

}

Core Views You Used

View What it displays Used in
Text("...") A string of text Every tab
Image("name") A photo or image from Assets Home, Story
Image(systemName: "...") An SF Symbol icon Home, tab bar labels
Button("...") { } A tappable button with action Fun Facts
Color.blue A solid colour block Favorites
Divider() A thin horizontal separator line Story, Favorites
Spacer() Flexible empty space that pushes views apart Favorites

4. The Three Stacks

Stacks are container views that arrange their children in a line. There are exactly three, and you need to know all of them.

VStack
Photo
Name
Tagline

Vertical — top to bottom

Used in: Home, Story, Fun Facts

HStack
Text

Horizontal — left to right

Used in: Home name row, Favorites

ZStack
Content

Depth — back to front

Used in: Fun Facts background

You can — and should — nest stacks inside each other. The Home tab uses a VStack as the outer container, with a VStack for the name card, inside which sits an HStack for the icon row. This nesting pattern is the core of all SwiftUI layout.

5. Modifiers

A modifier is a method chained onto a view that changes how it looks or behaves. You can chain as many as you like — each one wraps the previous result.

Text("Roan Venter")

.font(.custom(FontNames.courier, size: 40))

.foregroundColor(Color(red: 0.5, green: 0.25, blue: 0.1))

Order matters. Modifiers are applied from top to bottom. Adding .padding() before .background() means the background fills the padded area. Swapping them means only the text itself gets the background colour, not the space around it. Try both and see the difference.

Modifier Reference

Text / Appearance

  • .font(.largeTitle) — preset size
  • .font(.custom("Courier", size: 40)) — custom font
  • .fontWeight(.bold) — weight
  • .foregroundColor(.brown) — text colour
  • .italic() — italic style
  • .foregroundColor(.secondary) — grey

Layout / Shape

  • .padding(30) — space around view
  • .padding([.top, .bottom]) — selective padding
  • .background(Color(...)) — fill colour
  • .cornerRadius(15) — round corners
  • .shadow(color: .black, radius: 10)
  • .frame(maxWidth: .infinity)

6. Images: Resizing, Clipping, and Overlays

A plain Image view renders at its natural pixel size — often enormous. You almost always need to add at least .resizable() and a sizing modifier before anything else will work correctly.

The Standard Image Stack

Image("profilePhoto")
    .resizable()
    .scaledToFit()
    .clipShape(Circle())
    .overlay(
        Circle()
            .stroke(.yellow, style: StrokeStyle(lineWidth: 15))
            .shadow(color: .black, radius: 10)
    )

clipShape Options

Try each of these in place of Circle() — swap them one at a time to see the effect:

// Clip to a circle
.clipShape(Circle())

// Rounded rectangle (adjust cornerRadius to taste)
.clipShape(RoundedRectangle(cornerRadius: 20))

// Ellipse — like a circle but can be wider than tall
.clipShape(Ellipse())

// Capsule — rectangle with fully-rounded short ends
.clipShape(Capsule())

The overlay Modifier

.overlay() layers another view in front of the current one, perfectly aligned. This is how the yellow border ring works — a Circle drawn with .stroke() sits on top of the clipped photo.

StrokeStyle lets you control line width, dash pattern, and line cap style. For a simple solid border, StrokeStyle(lineWidth: 10) is all you need. Increase the number for a thicker ring.

7. Custom Fonts

Instead of hard-coding font names as raw strings scattered through your code, you can collect them in a helper struct called FontNames that lives at the bottom of HomeView.swift:

struct FontNames {
    static var americanTypewriter = "American Typewriter"
    static var arial              = "Arial"
    static var baskerville        = "Baskerville"
    static var chalkduster        = "Chalkduster"
    static var courier            = "Courier"
    static var georgia            = "Georgia"
    static var helvetica          = "Helvetica"
    static var palatino           = "Palatino"
    static var zapfino            = "Zapfino"
}

// Using it:
Text("Your Name")
    .font(.custom(FontNames.courier, size: 40))

Now if you want to change a font you only edit it in one place — and the autocomplete menu shows you all your options when you type FontNames..

Finding more fonts

Apple maintains a full list at developer.apple.com/fonts/system-fonts. Add any font name you find there as a new static var in FontNames, then use it anywhere in the app.

Font Size Presets

When you don't need a custom font, use SwiftUI's semantic size names — they respect the user's accessibility text-size setting:

Modifier Approx. size Typical use
.largeTitle 34pt Screen title (e.g. "Fun Facts", "Favorites")
.title 28pt Section headings
.title2 22pt Sub-headings (e.g. "Hobbies", "Foods")
.title3 20pt Minor headings
.headline 17pt bold Emphasis text, chip labels
.body 17pt Default body text
.subheadline 15pt Author bylines, supporting text
.footnote 13pt Small annotations
.caption 12pt Image captions, timestamps

8. SF Symbols

Apple ships over 5,000 icons free with every device — called SF Symbols. You access them by name using Image(systemName:):

"flag.pattern.checkered"
Used in Home tab
"person"
Home tab icon
"book"
Story tab icon
"star"
Favorites tab icon
"hand.thumbsup"
Fun Facts tab icon
"heart.fill"
Your Tab icon
"sparkles"
Decoration example
"trophy.fill"
Achievement ideas

Finding symbols in Playgrounds: Tap the symbols library button (✪) in the toolbar. You can search by keyword, browse categories, and tap to insert the code directly. Every symbol also has .fill, .circle, .square, and other variants.

9. Colour Models

SwiftUI gives you four ways to specify a colour. All four were used in this project:

// Named SwiftUI colours
Color.blue
Color.red
Color.green
Color.orange
Color.purple
Color.pink
Color.yellow
Color.mint
Color.brown
Color.cyan

// RGB — values between 0.0 and 1.0
Color(red: 0.5, green: 0.25, blue: 0.1)   // warm brown
Color(red: 0.13, green: 0.29, blue: 0.54)  // TKC navy

// Hue / Saturation / Brightness (HSB)
Color(hue: 0.9, saturation: 0.5, brightness: 0.9)

// Greyscale — 0.0 is black, 1.0 is white
Color(white: 0.75)

// Transparency via opacity
Color.blue.opacity(0.3)

RGB — Red, Green, Blue

Each channel is a decimal between 0.0 and 1.0 (not 0–255). Used in the Home tab name card background Color(red: 0.75, green: 0.75, blue: 0.75) — a neutral grey.

HSB — Hue, Saturation, Brightness

Often easier for picking vivid colours. Hue is the colour wheel angle (0–1), saturation is intensity, brightness is lightness. Used in the Favorites colour swatches.

10. ScrollView

When content might overflow the screen, wrap it in a ScrollView. By default it scrolls vertically — used in the Story tab. Pass .horizontal to make it scroll sideways — used in the Favorites foods row.

Vertical (Story Tab)

ScrollView {
    VStack(alignment: .leading) {
        Text("My Story")
            .font(.largeTitle)
        Text("Author")
            .font(.subheadline)
            .foregroundColor(.secondary)
        Divider()

        Text("Everyone knows that I just love …")
            .padding([.top, .bottom])
        Text("    Rugby. My favourite team is the ACT Brumbies.")
            .padding(.bottom)
    }
    .padding()
    .frame(maxWidth: .infinity)
    .background(in: RoundedRectangle(cornerRadius: 15))
    .padding()
}
.background(Image("Blue").opacity(0.5))

Horizontal (Favorites Tab)

ScrollView(.horizontal) {
    HStack(spacing: 30) {
        Text("🥐").font(.system(size: 48))
        Text("🌮").font(.system(size: 48))
        Text("🍣").font(.system(size: 48))
        Text("🍉").font(.system(size: 48))
        Text("🥖").font(.system(size: 48))
        Text("🍫").font(.system(size: 48))
    }
}
.padding()

Tip: Horizontal ScrollView is great for any list that could grow without bound — foods, hobby emojis, photo cards. It keeps the layout clean no matter how many items you add.

11. Group — the 10-View Limit

SwiftUI has a hard limit: no more than 10 direct children inside a single stack. If you add an 11th view, you'll get an error that says something like "Extra arguments at positions #11 in call".

The fix is Group. It doesn't change the visual output at all — it's purely an organisational wrapper that counts as one child, regardless of how many views are inside it.

// SwiftUI only allows 10 direct children in a stack.
// Wrap related views in Group{} to get around the limit.

VStack {
    Group {
        Text("My Story")
            .font(.largeTitle)
        Text("Roan Venter")
            .font(.subheadline)
            .foregroundColor(.secondary)
        Divider()
    }
    Group {
        Text("Everyone knows that I just love …")
            .padding([.top, .bottom])
        Text("    Rugby.")
            .padding(.bottom)
        Text("My super power is …")
        Text("    Teaching")
    }
    Group {
        // more content...
    }
}

Beyond fixing the limit, Group is also good practice for organising your code into logical sections — title block, body paragraphs, images — even when you haven't hit 10 views yet.

12. DisclosureGroup — Collapsible Sections

A DisclosureGroup shows a header that the user can tap to reveal or hide content. It was used in the Favorites tab to let users guess your favourite colours before revealing them.

DisclosureGroup {
    // Content shown when expanded
    HStack(spacing: 30) {
        Color.purple
            .frame(width: 70, height: 70)
            .cornerRadius(10)
        Color(hue: 0.9, saturation: 0.5, brightness: 0.9)
            .frame(width: 70, height: 70)
            .cornerRadius(10)
        Color.orange
            .frame(width: 70, height: 70)
            .cornerRadius(10)
        Spacer()
    }
    .padding(.vertical)

} label: {
    // The header — always visible
    Text("Guess my favourite colors")
        .font(.title2)
}
.padding()
.accentColor(.purple)

Collapsed (default)

Guess my favourite colors

Expanded (after tap)

Guess my favourite colors

The label: trailing closure is what's always visible — the header. The first trailing closure (before label:) is the content, revealed on tap.

13. Arrays

An array is an ordered list of values, all of the same type. In the Fun Facts tab, an array holds all the facts waiting to be shown:

// Declare an array of Strings
var allFunFacts = [
    "I have visited 8 different countries.",
    "I play guitar in my spare time.",
    "I coach a junior rugby team on weekends.",
    "I have read over 200 books."
]

// Pick a random element — returns Optional, so use ?? for a default
funFact = allFunFacts.randomElement() ?? "No facts available."

Key concepts from this usage

var allFunFacts = [...]

Declares a variable array of String values. The square brackets define the array; each item is a String in quotes, separated by commas.

.randomElement()

A built-in method on any Array that returns a random item. It returns an Optional — the value might be nil if the array is empty.

Optional — String?

randomElement() returns String? (Optional String) because the array could theoretically be empty. You must unwrap it before using it.

?? "No facts available."

The nil-coalescing operator. If the left side is nil, use the right side as a default. This is how you safely unwrap an Optional in one line.

14. @State and Button — Reactive UI

The Fun Facts tab is where the app comes alive. A @State variable stores what text is currently showing. A Button changes it. When the value changes, SwiftUI rebuilds the view automatically.

struct FunFactsView: View {
    var allFunFacts = [
        "I have visited 8 different countries.",
        "I play guitar in my spare time.",
        "I coach a junior rugby team on weekends."
    ]

    @State private var funFact = ""

    var body: some View {
        ZStack {
            Image("Green")   // background layer

            VStack {
                Text("Fun Facts")
                    .font(.largeTitle)

                Text(funFact)
                    .font(.title)
                    .frame(maxWidth: 400, minHeight: 300)

                Button("Show Random Fact") {
                    funFact = allFunFacts.randomElement() ?? "No fun."
                }
                .padding()
                .foregroundColor(.white)
                .background(.blue)
                .clipShape(RoundedRectangle(cornerRadius: 15))
                .font(.title2)
            }
            .padding()
        }
    }
}

How the data flows:

User taps Button
Button closure runs
funFact = allFunFacts.randomElement() ?? "..."
@State var changes
SwiftUI rebuilds
Text(funFact) view

This pattern — @State variable + Button + reactive Text — is the foundation of almost all interactive SwiftUI development. Master it here and you've understood something that scales all the way to professional apps.

private means

@State private var funFact = "" — the private keyword means this variable can only be accessed inside FunFactsView. Other views can't reach in and change it. This is good practice: keep state local to the view that owns it.

15. The Home Tab — Putting It All Together

The Home tab uses the most techniques at once. Here's the complete code your teacher wrote as a model — read through it and identify every concept it uses:

struct HomeView: View {
    var body: some View {
        VStack {
            Text("All About")
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding()

            Image("Family")
                .resizable()
                .scaledToFit()
                .clipShape(RoundedRectangle(cornerRadius: 26.0))
                .overlay(
                    RoundedRectangle(cornerRadius: 26.0)
                        .stroke(.yellow, style: StrokeStyle(lineWidth: 10))
                        .shadow(color: .black, radius: 10)
                )

            VStack {
                Text("Roan Venter")
                    .font(.custom(FontNames.courier, size: 40))
                    .foregroundColor(Color(red: 0.5, green: 0.25, blue: 0.1))

                HStack {
                    Image(systemName: "flag.pattern.checkered")
                        .foregroundColor(.brown)
                    Text("Grateful")
                        .font(.custom(FontNames.courier, size: 20))
                    Image(systemName: "flag.pattern.checkered")
                        .foregroundColor(.brown)
                }
            }
            .padding(30)
            .background(Color(red: 0.75, green: 0.75, blue: 0.75))
            .cornerRadius(15)
            .shadow(color: .black, radius: 10)
        }
        .background(Image("Blue"))
        .padding()
    }
}

Concepts in use in HomeView:

VStackHStackTextImageImage(systemName:).resizable().scaledToFit().clipShape().overlay()StrokeStyle.font(.custom())FontNames structColor(red:green:blue:).foregroundColor().padding().background().cornerRadius().shadow()

16. Your Own Tab

The final challenge is YourTab.swift — a blank view that starts with just a "Hello, World!" placeholder. You design it from scratch using everything you've learned:

// Start with the placeholder:
struct YourTab: View {
    var body: some View {
        Text("Hello, World!")
    }
}

// Replace it with your own design:
struct YourTab: View {
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: "trophy.fill")
                .font(.system(size: 60))
                .foregroundColor(.yellow)

            Text("Achievements")
                .font(.largeTitle)
                .fontWeight(.bold)

            Text("Year 10, 2025")
                .foregroundColor(.secondary)
        }
        .padding()
    }
}

Once your tab is built, wire it into ContentView.swift by adding it to the TabView:

// In ContentView.swift, add your tab inside TabView:
YourTab()
    .tabItem {
        Label("My Tab", systemImage: "heart.fill")
    }

17. App Settings

The final polish step is configuring the app's identity in App Settings:

1

Open the left sidebar

Tap the sidebar button in the top-left of Swift Playgrounds.

2

Tap App Settings

Near the top of the sidebar — this opens the app configuration panel.

3

Change the app name

Replace "About Me" with your own title — this appears under the icon when installed.

4

Set the accent colour

This tints interactive elements (buttons, toggles, DisclosureGroup arrows) across the whole app.

5

Change the app icon

Tap "Placeholder" to pick from system options, or tap "Custom" to upload your own design from Photos.

Completion Checklist

Key Terminology

@main — Marks the app entry point struct
App protocol — Required by every SwiftUI app; needs a body Scene
WindowGroup — The scene type for a standard app window
View — Any visual element — the core building block of SwiftUI
body — The required computed property that returns what to display
struct — The data type used to define every custom view
modifier — A method chained on a view to change appearance/behaviour
VStack / HStack / ZStack — Vertical, horizontal, and depth layout containers
TabView — Container that provides a tab bar navigation interface
.tabItem — Modifier that sets a tab's icon and label in the tab bar
@State — Property wrapper that triggers view rebuilds when changed
Button — A tappable view with a label and an action closure
Array — An ordered list of values of the same type, written with []
.randomElement() — Returns a random item from an array (returns Optional)
?? — Nil-coalescing: use left side, or fall back to right if nil
Optional — A value that might be present or might be nil (absent)
Group — An invisible container that groups views without visual effect
ScrollView — A container that lets its content scroll — vertical or horizontal
DisclosureGroup — A collapsible section with a tappable header
SF Symbols — Apple's built-in icon library — used with Image(systemName:)
clipShape — Crops a view to a shape (Circle, RoundedRectangle, etc.)
overlay — Layers a view on top of another, perfectly aligned
StrokeStyle — Describes the appearance of a stroked path (line width, dash)
FontNames struct — A helper struct holding font name strings as static constants
🏠

Project Connection

In Simpson's House...

About Me is the training ground for Simpson's House. Every SwiftUI concept you learn here — @State, modifiers, stacks, TabView — is used directly in the Simpson's House controller app. You're not learning SwiftUI in isolation; you're building the skills you'll need to customise a real IoT interface.

@State → @StateObject

Local state scales up to app-wide state

The @State you use in About Me for a fun fact button becomes @StateObject in Simpson's House — the same reactive principle, now powering a live MQTT connection.

Modifiers & Stacks

DeviceCard uses the exact same layout techniques

The VStack, HStack, .padding(), .background(), and .clipShape() patterns from About Me are used to build the device control cards in Simpson's House.

TabView

About Me's 5-tab structure mirrors the app architecture

Building a multi-tab app in About Me teaches you how SwiftUI manages multiple views — the same skill needed to navigate between the device controls, logs, and connection screens in Simpson's House.

Conditional Views

Showing/hiding content based on state

In About Me you use @State to show a random fact. In Simpson's House, the same pattern drives the entire conditional UI: if connected, show device controls; otherwise show the connection prompt.

Explore the full Simpson's House project →