In our inaugural post on how to get started with SwiftUI, I explored a brief overview of one of our favourite announcements from WWDC19 – from defining what SwiftUI actually is to how we can create new projects with it. As we delve deeper into the API, however, I want to take the time to consider what the introduction of SwiftUI means for the world of declarative programming. In this post, we’ll be doing just that – from exploring what it means for Interface Builder to looking at all of the issues SwiftUI actually solves.
Could the introduction of SwiftUI signal an end of an era, not only because it’s a Swift-only framework and Apple have decided to move on from Objective-C, but because it quite possibly marks the end of the rivalry between Interface Builder supporters and detractors?
Here at We Are Mobile First, the team is comprised of both supporters and detractors but I won’t be taking any sides in this post! Interface Builder is part of Xcode, Apple’s toolkit for developers that allows developers to create interfaces for apps using graphical user interfaces. It is, in a sense, declarative programming with a few key features missing.
As you may already know, Interface Builder creates a file (XML) that can then be turned into Objective-C objects. This XML file has strings for class names and selectors and Objective-C runtime allows us to instantiate these classes and call them methods. This is one of the pivotal features that had been missing from the Swift language since it was first introduced in 2014.
New features were announced at the last WWDC that mean we can now create domain-specific languages and achieve maximal declarative programming via Swift.
As I mentioned in my post on how to get started with SwiftUI, SwiftUI doesn’t signal the death of UIKit but it does, however, put an end to Interface Builder.
What SwiftUI brings us is the best of both worlds as it allows us to preview our interface like Interface Builder did but it goes even further and allows us to also preview state.
But wait, how does SwiftUI do that? Let’s look at the LoginView example I shared in the last post in this series…
Note that we tagged the authenticationDidFail, username and password variables with @State.
Since specific views are tagged, SwiftUI can track the node in the hierarchy associated with the change and avoid re-rendering parent nodes. SwiftUI will avoid re-rendering children that are unchanged, even when the parent nodes change. It’s unclear as to how this is achieved as there is, as of yet, no documentation on the subject. SwiftUI Views aren’t equatable so I am guessing there’s a memory diffing which I still need to confirm.
This process eliminates a common problem when building apps as the UI is automatically updated when the state is updated. It also solves one of the biggest issues with imperative view programming: lifecycle.
So if we run Xcode, we can preview multiple states of our app and we can also use the inspector to customise our views like we did with Interface Builder.
Go ahead and Command-click the UI elements to bring up the editing popover and choose Inspect (see fig. 1).
Once selected, you’ll be able to see its attributes in the attribute inspector (fig. 2).
I’m not going to say AutoLayout is dead, as it will still be used behind the scenes, but it’s no longer showcased in SwiftUI. The layout is mainly composed of horizontal and vertical stacks. No constraints. SwiftUI takes care of padding alignment and sizing.
The code above stacks text both horizontally and vertically. If we want to add spacing, we can do so by providing the value in the init:
To add padding around views, we can use the padding modifier. You can customise the padding and control how much padding you want to add…
and you can also specify the side of the view where you want to add the padding:
What happens when you want to customise your layouts a little bit more? You need to build custom views. I encourage you to watch the WWDC session 237, Building Custom Views with SwiftUI.
Let’s start by understanding what the parent-child relationship looks like in SwiftUI.
By looking at Figure 3, we can see that the Hello World Text is at the bottom of the view hierarchy. Above it is the ContentView, which is the same bounds as its body, and in this case it’s just the text. Finally. there’s the RootView which is the device dimensions minus the safe area.
So what happens here is that the parent view proposes its child a size and because the parent is the Root it offers the text the whole safe area. In SwiftUI, however, the child chooses its own size so the Text widget just takes the size it needs to display the content (because in SwiftUI the bounds of the text never stretch beyond the height and width of its displayed lines). One important thing to mention is that in SwiftUI there’s no way to force a child size from the parent. Finally, the parent places its child in the parent Coordinate space.
This was just a simple example but this is how the whole layout will behave based on the parent-child interactions. Since every view controls its size, it means that when we build a view we are in control of how and when it resizes.
The above example is nice but, to be honest, not everything in life is that simple. There are times when we require more control over the layout and our views.
Views, by default, will take as much space as they need but we can control the frame using a frame modifier. For example, as follows, we can create a VStack with Text at the bottom and a rectangle with a view that takes up a 150×100 area:
Here’s the result…
As you can see, the view Indicator() doesn’t worry about size. It only does one job. It draws a green rectangle. SwiftUI is more than capable of figuring out where and how big the parent wants it. In this case, the containing VStack is the parent view.
We can also give a frame an indicator. It will look a little something like this:
Now let’s explore one of the tools that gave us more control over the layouts, GeometryReader.
If you wanted two views to take up half the available width on the screen, this wouldn’t be possible using hard-coded values because we don’t know ahead of time what the screen width will be. To solve this, GeometryReader provides us with an input value that informs us of the available width and height. We can then use that to make whatever calculations we need.
In order to create two views that take up equal width, we could divide the available space in half like so:
When looking at the above example, you’ll see a closure with a parameter named ‘geometry’. This is a GeometryProxy to assess the size and coordinate space for a container view. From its definition, we can allow access to the frame which lets us obtain a rect with different coordinate spaces using .local, .global or .named coordinates spaces.
SwiftUI is a declarative framework that ensures that initial construction is much simpler and more aesthetically pleasing and accessible. This, in turn, makes testing much more straight-forward as we’re able to see the updated views immediately in the previews.
In the next instalment in this series, I’ll be looking at how to handle advanced state in SwiftUI apps before diving deeper into how to migrate from UIKit to Swift UI.
Have you experimented with SwiftUI yet? If so, what are your thoughts? Tweet us and we’ll be sure to retweet the responses!
We Are Mobile First is a digital product agency based in Barcelona helping to transform businesses in a mobile-first world. Follow us on Twitter, LinkedIn and Medium to be notified of our future posts and stay up-to-date with our company news.
We share weekly content on everything from how to successfully lead a software development team to Apple’s Combine: an introduction to the reactive framework.
(Hero image credit: Apple Developer)