SwiftUI foundations

Introduction to SwiftUI

Learn how SwiftUI uses declarative views, state, modifiers, scenes, and previews to build interfaces across Apple platforms.

SwiftUI is Apple’s framework for building user interfaces with Swift.

You write the interface once and run it on iOS, iPadOS, macOS, watchOS, tvOS, and visionOS. Many views adapt their look and behavior to the platform.

SwiftUI is declarative. You describe what the screen should show for the current data. When the data changes, SwiftUI updates the affected views for you.

This is different from creating every view by hand and telling it how to change after each event.

Does SwiftUI replace UIKit and AppKit?

No.

SwiftUI is where I’d start any new interface today, but UIKit, AppKit, and WatchKit are still fully supported.

You can drop a SwiftUI view inside an existing UIKit or AppKit app, and wrap a UIKit view or view controller to use it inside SwiftUI.

This lets you adopt SwiftUI one screen at a time. It also saves you when SwiftUI lacks a feature you need.

The structure of a SwiftUI app

A SwiftUI app starts with a type that conforms to the App protocol:

import SwiftUI

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

The @main attribute marks the entry point of the app.

The body property returns a scene. A scene is a part of the interface whose lifecycle the system manages for you.

WindowGroup is the usual main scene. It holds the root view, ContentView in this example.

Creating a view

You create a custom view by defining a struct that conforms to the View protocol:

import SwiftUI

struct ContentView: View {
  var body: some View {
    Text("Hello, world!")
  }
}

The View protocol asks for one thing: a body computed property. Whatever body returns shows up on screen.

To show more than one view, arrange them in a stack:

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello, world!")
      Text("Welcome to SwiftUI")
    }
  }
}

VStack lays out its children vertically. SwiftUI also gives you HStack, ZStack, lists, grids, forms, and many other containers.

What does some View mean?

You’ll see some View in almost every SwiftUI file.

some creates an opaque result type. It tells callers “you get a value that conforms to View”, without spelling out the long concrete type the view hierarchy produces.

The compiler still knows the exact type and checks it at compile time. And thanks to SwiftUI’s result builder, you can write several child views and if statements inside body.

You almost never need to write the concrete type yourself.

Configuring views with modifiers

You customize a view by applying modifiers:

Text("Hello, world!")
  .font(.title)
  .bold()
  .padding()

Each modifier returns a new view that wraps or configures the previous one.

Because of that, order matters. Adding a background before padding gives a different result than adding it after. More on this in the next lesson.

Connecting a view to state

The interface gets interesting when it changes with the data.

For a small value owned by one view, use @State:

struct CounterView: View {
  @State private var count = 0

  var body: some View {
    VStack {
      Text("Count: \(count)")

      Button("Add one") {
        count += 1
      }
    }
  }
}

When the button changes count, SwiftUI re-renders the parts of the screen that read it. You never call an update method.

Two habits to pick up now. Declare state as private, and keep it in the highest view that owns the value. Pass a binding to a child view when that child needs to change it.

For application data shared by many views, use an observable model instead. @State is for temporary interface state, not permanent storage.

Views are descriptions

A SwiftUI view struct is a lightweight value describing part of the interface. SwiftUI creates that value and evaluates its body many times.

So don’t treat a view struct like a long-lived view controller. Keep mutable data in state or in a model, and never start a network request or other side effect while computing body.

When the view needs to do work, use lifecycle modifiers like task, onAppear, and onChange.

Previewing a view in Xcode

Xcode shows a live preview right next to your code:

#Preview {
  ContentView()
}

I use previews all the time. They let you check different data, screen sizes, accessibility settings, and dark mode without tapping through the whole app.

They don’t replace running the app on simulators and real devices, though.

Starting a SwiftUI project

Open Xcode and create a new app project. When asked which interface to use, pick SwiftUI.

Xcode generates an App type, a WindowGroup, and a first view. Edit that view and run the project.

Check the deployment target before using a new SwiftUI API. SwiftUI grows with every Apple platform release, and an API in the current SDK might need a newer OS than your app supports.

The official SwiftUI documentation covers the whole framework, and Apple’s SwiftUI pathway is a good place to keep learning.

Lesson completed