Introduction to SwiftUI

Key takeaways from Apple’s SwiftUI Tutorials & Documentation

Ashok Khanna
5 min readDec 10, 2021

This is a short summary of the key takeways against Apple’s SwiftUI Tutorials and Documentation. It is mostly copy and paste from the official Apple documentation.

App Structure and Behaviour

In this section, we cover the entry point and top-level organisation of apps. Apple’s documentation can be found here. An app that uses the SwiftUI app life cycle has a structure that conforms to the App protocol. An example is below.

import SwiftUI@main
struct LandmarksApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

In the above, you can see three key elements of an App object:

  • You need to import SwiftUI in any files where you work with SwiftUI objects and protocols
  • The @main attribute marks the entry point of the application (noting that you can have only one entry point in your app)
  • The LandmarksApp structure conforms to the App protocol. This requires you to define variable body that conforms to the Scene protocol. Each scene contains the root view of a view hierarchy and has a life cycle managed by the system. SwiftUI provides some concrete scene types to handle common scenarios, like for displaying documents or settings. You can also create custom scenes.

WindowGroup is a scene that is used as a container for a view hierarchy presented by your App and is used to present a group of identically structured windows. Every window created from the group maintains independent state. You typically use a window group for the main interface of an app that isn’t document-based. For document-based apps, use a DocumentGroup instead.

Understanding Views

SwiftUI offers a declarative approach to user interface design. With a traditional imperative approach, the burden is on your controller code not only to instantiate, lay out, and configure views, but also to continually make updates as conditions change. In contrast, with a declarative approach, you create a lightweight description of your user interface by declaring views in a hierarchy that mirrors the desired layout of your interface. SwiftUI then manages drawing and updating these views in response to events like user input or state changes.

SwiftUI provides tools for defining and configuring the views in your user interface. You compose custom views out of built-in views that SwiftUI provides, plus other composite views that you’ve already defined. You configure these views with view modifiers and connect them to your data model. You then place your custom views within your app’s view hierarchy.

Declaring Views

To conform to the view protocol, you must supply a body property, which in itself needs to be of type view:

struct MyView: View {
var body: some View {
Text("Hello, World!")
}
}

Views that take multiple input child views, like the stack in the example above, typically do so using a closure marked with the ViewBuilder attribute. This enables a multiple-statement closure that doesn’t require additional syntax at the call site. You only need to list the input views in succession.

Modifying Views

To configure the views in your view’s body, you apply view modifiers. A modifier is nothing more than a method called on a particular view. The method returns a new, altered view that effectively takes the place of the original in the view hierarchy.

For example, you can change the font of a text view by applying the font(_: modifier:

struct MyView: View {
var body: some View {
VStack {
Text("Hello, World!")
.font(.title)
Text("Glad to meet you.")
}
}
}

Interfacing with AppKit, UIKit and WatchKit

It is very useful to wrap traditional AppKit, UIKit and WatchKit objects as representable objects to embed them within SwiftUI views. A representable object wraps the designated view or view controller and facilitates communication between the wrapped object and your SwiftUI views. More details can be found on Apple’s documentation for Framework Integration.

For example, to represent UIKit views and view controllers in SwiftUI, you create types that conform to the UIViewRepresentable and UIViewControllerRepresentable protocols. Your custom types create and configure the UIKit types that they represent, while SwiftUI manages their life cycle and updates them when needed. For AppKits views and view controllers, you create types that conform to the NSViewRepresentable and NSViewControllerRepresentable protocols.

You can find examples on how to do this online, such as in the official Apple tutorial or this tutorial by Hacking with Swift.

State and Data Flow

SwiftUI offers a declarative approach to user interface design. As you compose a hierarchy of views, you also indicate data dependencies for the views. When the data changes, either due to an external event or because of an action taken by the user, SwiftUI automatically updates the affected parts of the interface. As a result, the framework automatically performs most of the work traditionally done by view controllers.

Credits for this photo and most of this guide go to Apple

The framework provides tools, like state variables and bindings, for connecting your app’s data to the user interface. These tools help you maintain a single source of truth for every piece of data in your app, in part by reducing the amount of glue logic you write. Choose the tool that best suits the task you need to perform:

  • Manage transient UI state locally within a view by wrapping value types as Stateproperties.
  • Connect to external reference model data that conforms to the ObservableObjectprotocol using the ObservedObject property wrapper. Gain access to an observable object stored in the environment using the EnvironmentObject property wrapper. Instantiate an observable object directly in a view using a StateObject.
  • Share a reference to a source of truth — like state or an observable object — using the Binding property wrapper.
  • Distribute value data throughout your app by storing it in the Environment.
  • Pass data up through the view hierarchy from child views with a PreferenceKey.
  • Manage persistent data stored with Core Data using a FetchRequest.

Due to the importance of this topic, I have written a separate introduction on managing data within SwiftUI.

Adding Interactivity with Gestures

Gesture modifiers handle all of the logic needed to process user-input events such as touches, and recognize when those events match a known gesture pattern, such as a long press or rotation. When recognizing a pattern, SwiftUI runs a callback you use to update the state of a view or perform an action.

Add Gesture Modifiers to Views

Each gesture you add applies to a specific view in the view hierarchy. To recognize a gesture event on a particular view, create and configure the gesture, and then use the gesture(_:including:) modifier:

struct ShapeTapView: View {
var body: some View {

let tap = TapGesture()
.onEnded { _ in
print("View tapped!")
}
return Circle()
.fill(Color.blue)
.frame(width: 100, height: 100, alignment: .center)
.gesture(tap)
}
}

More details can be found in Apple’s official documentation on gestures.

--

--

Ashok Khanna

Masters in Quantitative Finance. Writing Computer Science articles and notes on topics that interest me, with a tendency towards writing about Lisp & Swift