Introduction
This text was prepared for publication in March 2020. As a result, a lot of the code and techniques in this book are out of date. It is provided as a historical curio, and does not provide accurate guidance on using SwiftUI today.
SwiftUI arrived to the surprise of many on June 3, 2019 at Apple’s WorldWide Developer Conference (WWDC) in San Jose, California. There had been some rumblings suggesting a new UI framework in the prior couple of years, but most attention was focussed on the Catalyst project, which would allow iPad applications to be built for and deployed on macOS. When that was officially announced at WWDC, everyone relaxed, not expecting anything else along those lines. Then came the big reveal: there was a new UI framework written entirely in Swift, and it worked on all of Apple’s platforms.
This new framework hit all the right buttons, reflecting as it did much of the programming world’s zeitgeist of the prior few years:
- The Swift Programming Language
- Swift was becoming popular, and its design implemented a number of features that Apple’s older Objective-C language did not, such as true generics, protocol-oriented programming, built-in block syntax, first-class functions, and high-level value types with member functions and visibility.
- Immutable Types
- By making use of immutable types, a lot of the complexity of dealing with data in multi-threaded applications disappears. Data changes atomically at a larger logical scale, rather than by smaller piecemeal updates.
- Declarative Syntax
- SwiftUI’s users would be able to describe their interfaces in a declarative manner (one of these on top of one of those, with a blue background and a picture of a cat) rather than the usual iterative approach (create a view, set its background color, get a picture of a cat, make it this big, move it over there). The syntax was more terse, and more closely matched the logical layout of the interface, with details neatly tucked away elsewhere.
- Functional Design
- Much of SwiftUI’s appeal came thanks to its judicious use of functional programming conventions, using first-class methods and blocks as part of the UI definition. Amongst other things, this would enable closure-based event handling code to be written at the point of declaration, where it’s easy to see and to update.
A Little UI API History
Since the advent of the GUI in the early 1980’s there have been several different ways of describing your user interface, from the original Smalltalk implementation at Xerox PARC through Mac OS, Windows, and Java, up to UIKit and SwiftUI today. Smalltalk and NeXTstep embraced a fully object-oriented approach to user interface design, while the classic Macintosh and Microsoft Windows used procedural APIs implemented in C. Java, by its nature, used an object-oriented system, albeit one built on top of the procedural systems provided by the platforms on which it ran.
Working with the interface itself was different on each platform, but the Interface Builder included with NeXTstep’s developer tools set the course for the rest of the industry: drag-and-drop layout of user interface elements. Interface Builder would create serialized representations of the UI, while on other platforms the tools generally generated actual code in C or Java. Eventually the UI-as-data idea began to expand, though, and we saw Microsoft use an XML syntax to define UIs for its .NET family of languages, while Java added JavaFX.
The common thread running through all these APIs is their iterative format. Though both object-oriented and procedural APIs existed, they all used an instructional, step-by-step means of describing properties and changes. In essence the programmers using them were writing precise recipes: “take this, move it here, give it this color, rotate it, then put this other thing on top, add an image,” and so on. Tools such as Interface Builder, Windows Forms, and JavaFX were providing a simpler way to encode these instructions, but anything beyond the purview of those tools would necessitate a switch to a functionally very different approach to solving the same problem.
The Declarative UI Paradigm
A primary benefit of a declarative user interface API is that the simpler, declarative model is used everywhere. In code, and in interface description files. In fact, with SwiftUI, only code is used, along with some new language features you’ll meet later, to allow that code to visually mimic that layout to some degree in the same manner as, say, an XML-based hierarchical description format. This means that there is only one set of concepts and tools to learn, and only one way to define and interact with your interface model.
Apple illustrates the difference between the iterative and declarative paradigms using the metaphor of a food order. In iterative style, you describe the instructions on how to make the food, as if describing the entire process over the phone: “finely chop an onion, two cloves of garlic, and a stick of celery, then add to the pan with a little olive oil, sauté for five minutes; measure 250g arborio rice;” etc. You’re effectively writing a recipe, and relying on the chef knowing a few things, like how to chop an onion. Declaratively, you’re making an order: “a risotto with fennel and extra parmesan.” The person you’re talking to knows how to make a risotto, you don’t need to tell them. You just lay out the specifics of your own case.
That last sounds somewhat familiar: it’s the same idea behind the frameworks approach espoused by the AppKit and UIKit frameworks. You don’t need to write a main() method that initializes different parts of the system, you don’t need to write an event-processing loop, you don’t even need to write a function that would receive raw events and dispatch them. The framework handles that, and does the normal things for you—your app launches and runs without anything special on your part. What you provide is the customization: the app will ask “the last window has been closed; should I quit now?” and you will answer yes or no, but only if you have a preference. The app will ask “what items should I display in this table?” and you’ll provide the data.
The declarative model used by SwiftUI, then, is a somewhat natural extension of the existing framework model. The framework knows how to put things onscreen, and how to work with them; you simply need to specify some specifics of what should go in what order. The instructions are, ultimately, still there, but they’re tucked out of sight. If you need to do something very complex and specific, then (to go back to the earlier metaphor) you write a recipe and give that to the cook, then you can ask the cook to include that recipe in your dish: poached salmon, rather than grilled.
Declarative code also fits mental models quite snugly. For example, here’s a list, where each item contains a stack of two text fields, one on top of the other, with these fonts:
List {
VStack {
Text("The Title").font(.title)
Text("The Subtitle").font(.subtitle)
}
}
This code defines a button with some text and a blue background. It also includes an action that plays a sound when the user clicks or taps the button:
Button(action: { playSound() }) {
Text("Title")
}.backgroundColor(.blue)
Finally, this code display an image within a white circular border with a drop shadow behind it:
Image("myImage.png")
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
Example Application
In the remainder of this book, you’re going to use SwiftUI to build an iOS application named “Do It.” You’ll use a small value-type data model to build the app for the iPhone initially, but then you’ll move on to iPadOS and its greater feature set, adding functionality all the while. Over the course of the book, you’ll assemble this application in the following steps:
- Chapter One: Layout and Presentation
- Here, you’ll build a simple todo-list management app showing data in a master/detail format.
- Chapter Two: Application Data in SwiftUI
- This chapter introduces the use of the SwiftUI
@Statetype and how it’s used to drive updates to your interface. - Chapter Three: Modifying Application Data
- SwiftUI’s tools for handling state and data modification form the focus of this chapter as you implement editing tools for our data set.
- Chapter Four: List Mutation
- Implement filtering for your to-do lists, along with an update to support delivery of predicates from higher in the view hierarchy.
- Chapter Five: Custom Views and Complex Interactions
- SwiftUI provides special tools for reacting to user input and adapting your views’ size and location dynamically, all in a concise declarative syntax. Here you’ll learn how these work and what you can do with them.
- Chapter Six: Making the Most of the Canvas
- In this chapter you’ll learn how to make good use of the Xcode canvas as a development tool, using it to preview the effect various system settings have on your application as you implement support for dark mode and dynamic text sizes.
- Chapter Seven: SwiftUI on iPadOS
- In this chapter you’ll investigate the additional features of iPadOS, contrasting with the similarities to the existing iPhone application. You’ll work with multiple windows, keyboard and pointer support, different display options, and lay the groundwork for drag and drop ready for the next chapter.
- Chapter Eight: Implementing Drag and Drop
- iPadOS and macOS both support transfer of data by dragging items into and out of your application. You’ll focus on iPad as you make use of several different approaches to getting information into and out of the app, implement gestures to open new windows, and move data between those windows.
- Chapter Nine: Core Data and Combine
- So far your application has been defined using only Swift structure types and a global data store. Many applications deal with a much more complex data store, so here you’ll move to a more easily-modified data model defined in Core Data. You’ll use SwiftUI’s Core Data tools to bring in and display your new data, and you’ll see how to make good use of CoreData’s predicates to drive your interface.