SwiftUI: Understanding Declarative Programming
Confused by declarative programming? Don’t worry, I’ve got you covered.
According to Apple, SwiftUI is an amazing declarative programming framework for constructing user interfaces on iOS and other Apple platforms. It says so right on the box.
But what does “declarative” mean?
Well, we could start by debating declarative programming vs. imperative programming, but that just kicks the bucket further down the road, as we now need to define the term “imperative”.
Instead, I’d prefer to simply substitute a word in my original sentence.
SwiftUI is an amazing functional programming environment for constructing user interfaces.
So let’s roll with that definition and see where it takes us.
Some people see the words “functional programming” and start getting worried.
That’s mostly because too many functional programmers start throwing around phrases like pure functions or terms like monads and lambdas and then start reaching for the whiteboards to describe the math behind it all…. which is when your eyes start glazing over and you start looking for the nearest exit.
Don’t worry, we’re not going to do that here.
After all, we already know what functions are. Functions are basically encapsulated blocks of code that take in values and return results. Done.
In SwiftUI, those functions return the pieces and parts that define our user interface. Or to put it another way, we use those functions to declare our interface.
Starting to make sense?
In SwiftUI, most of the pieces and parts that make up our interface are called Views. Is there some text? It’s a View. An image? Another View. Is it a list? You guessed it. It’s a View that’s a list of Views.
But unlike UIKit’s heavily overloaded, Objective-C dispatching UIViews, SwiftUI views are really just little pieces of information that, when combined together, describe our interface.
And how do we combine them together?
Nested Functions for the Win
Another aspect of functional programming is that we typically nest functions inside of functions. We do the same in SwiftUI.
Don’t leave yet!
It might seem strange, but if you stop to consider it, you’re already used to thinking in those terms. Our UINavigationController contains a UITableView, whose lists contain UITableViewCells, which might contain UIStackViews that contain UILabels and UIButtons.
One interface element contains other elements, which contains other elements, until voila! We’ve described our interface and it’s onto the next screen, where we do the same thing.
In the SwiftUI example below, the interface returned by the view body boils down to a NavigationView that contains a list, whose elements are HStack views that in turn contain an image and some text.
The syntax may look a bit strange, but that’s supported by some new features in Swift 5.1.
Boom. We’ve just declared our first fully functional SwiftUI interface, in just 15 lines of code.
“Hold on there, sport!” you say. “Not so fast. What if I want my text to be red? And I need a title there, not body text, and…”
Got it. You want to modify the default look and feel.
So how do we do that?
Yeah, I set up that one. But it’s the term Apple uses and it fits, so we’ll stick with it.
Technically speaking, a modifier is an operator on a view… that returns another view…. which can be modified, ad infinitum (Or until you run out of memory 😉).
From a practical standpoint, all you’re doing is using SwiftUI’s views and operators to describe the interface to SwiftUI, in almost exactly the same terms you’d use to describe the interface to another developer.
“I want my disclaimer text there, use the footnote font and size, and make it the secondary color so it works with dark mode.”
That’s one interface element with two modifiers.
Text("Your mileage my vary.")
Describe the above interface to SwiftUI, and it does the rest, laying out the text with padding and spacing appropriate to the font size and the device your app is running on.
It automatically takes care of dynamic text and accessibility font sizes. It automatically swaps the color so it looks correct in both light mode and dark mode. It supports internationalization and text direction.
In short, it does its best to manage the fussy little details, and it does it on each and every element and part of your interface. This frees you, the developer, to focus on your app’s features, business logic, and data.
Speaking of which…
What About Our Data?
Most apps are about data. Sometimes the data is built into the app, as shown in our disclaimer example above, but usually, our data comes from an external source, a database, an API, or sometimes even from information directly entered into the app.
When we define a user interface element in SwiftUI, we also tell that element where to find the data it needs. Sometimes that information is static, like the string in the text view shown above.
And sometimes that information is driven by our data. In the following example, we’ve modified our list view to use the data that’s passed to it when it’s initialized elsewhere in the app.
The first parameter to list tells it exactly where to find its data.
List will then pull each element from the array and pass it into the closure, which in turn uses it to build and return a view that displays that element.
All of this happens with no data sources or delegates needed. The above code could be a complete, working screen in your app.
Sometimes our information is dynamic, subject to change, and our interface should adjust accordingly.
The above implementation is practically identical to the first, but with a major difference, the
@Binding property wrapper added to our list and title variables.
Binding tells SwiftUI to watch for changes to the list array. Should its owner update the array’s contents, they’ll notify us and our list view will update and render itself — automatically.
You should note that List is an extremely smart little view. Should the owner of the list insert an item and delete another, on learning of a change List will scan the updated list, compare it to the previous list, and then generate the proper insertion and deletion animations for you.
Also, note that we’re also now passing in the title we want in the navigation bar. The title variable is also bound, so whenever that bit of data changes, our titlebar will automatically update to match.
Binding is just one of the ways you manage and maintain your application’s state in SwiftUI. A full discussion of all of those is beyond the scope of this article, so stay tuned.
One additional aspect that should be mentioned is the environment.
In SwiftUI, the environment is a global set of variables that work to describe the enviroment in which the app is running. Things like are we in Light or Dark mode? What’s the current vertical size class? Horizontal?
These variables can be queried to determine that current state of the device and the app so that you can do the right thing at the right time.
You can also add your own information to the environment and access it later on, further down the view heirarchy.
@EnvironmentObject var settings: UserSettings
@Environment(\.colorScheme) var colorScheme: ColorScheme
Again, this is getting a bit beyond the scope of this article, but you need to know that it’s there.
Under the Hood
So, you declare your interface and you point SwiftUI to your data… and SwiftUI does the rest.
Each View, the state of its modifiers, its data, and the current state of the environment is combined and aggressively reduced down to a set of layout and rendering commands. The result is then presented to the user.
SwiftUI then waits for changes to the data. Should they occur, SwiftUI walks through the affected views and builds a new layout tree, compares any changes it finds against the current tree, and then renders the portions of the view that need updating.
This makes SwiftUI blazingly fast compared to UIKit. In fact, most of the view and animation rendering is done directly by Metal.
Yep, it’s easy to do animation in SwiftUI. In fact, determining what animations may be needed for view state transitions is built directly into the tree comparison process.
Some animations, like List insertion and deletion and those pertaining to view modifier state changes, are built directly into the system.
And all of them can be modified and customized if needed.
So that’s it. Hopefully, you now understand what Apple means when it says that SwiftUI is a new, declarative programming framework for constructing user interfaces on iOS and other Apple platforms.
And that the amazing modifier [sic] is well earned.
As always, leave any questions or comments below, and I’ll do my best to answer them, either directly or in a future article.
The SwiftUI series continues with:
- SwiftUI: Deep Inside NavigationView
- Best Practices in SwiftUI Composition