Is it the right time to switch to SwiftUI?
Apple announced at the last WWDC "The best way to build an app is with Swift and SwiftUI". While the language is unanimous in the community, the recent framework for creating interfaces SwiftUI is at the heart of the debate. Its advantages are numerous, but it seems to be made more for the future than for the present in which we still have to rely on UIKit.
Since the release of iOS in 2007, apps have only had UIKit to create and manage interfaces. Despite its regular improvements, including the possibility of using different layout engines (manual positioning of views, then relative thanks to AutoLayout), it suffers today by its lack of modernity and struggles to seduce.
Presented as a new revolution since its announcement at WWDC 2019, SwiftUI, with its declarative syntax, is supposed to drastically reduce the effort required to build a "simple " interface. However, going outside the framework of this simplicity planned by Apple means exposing yourself to a considerable increase in development complexity.
Let's have a look at SwiftUI and discover our feedback.
How SwiftUI works
An app logically presents a hierarchy of views. Here is an example of how to create this hierarchy, where we can see the interest of a declarative UI framework:
let imageView = UIImageView(
image: UIImage(
systemName: "flame.circle",
withConfiguration: UIImage.SymbolConfiguration(pointSize: 60, weight: .light)
)
)
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .red
let label = UILabel()
label.text = "Hello, world"
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
let labelContainerView = UIView()
labelContainerView.backgroundColor = .red
labelContainerView.addSubview(label)
labelContainerView.addConstraints([
labelContainerView.topAnchor.constraint(equalTo: label.topAnchor, constant: -16),
labelContainerView.bottomAnchor.constraint(equalTo: label.bottomAnchor, constant: 16),
labelContainerView.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: -16),
labelContainerView.trailingAnchor.constraint(equalTo: label.trailingAnchor, constant: 16),
])
let stackView = UIStackView(arrangedSubviews: [imageView, labelContainerView])
stackView.axis = .vertical
stackView.spacing = 20
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
view.addConstraints([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
VStack(spacing: 20) {
Image(systemName: "flame.circle")
.font(.system(size: 60, weight: .light))
.foregroundColor(.red)
Text("Hello, world!")
.foregroundColor(.white)
.padding()
.background(Color.red)
}
This paradigm shift also induces a new way of designing your program. Inspired by Declarative UI frameworks like React, SwiftUI is designed for reactive programming, which is becoming more and more popular, and for which UIKit was not originally designed.
The app reacts to an event (user action, webservice return, timer, etc.) to change its state accordingly. Each time the state is modified, SwiftUI creates a new view tree and compares it to its previous version. Thus, only the modified views are regenerated.
Creating the tree and checking for differences is inexpensive, using the power of Swift's struct (value type). Its operation is very well explained by Apple in this video: Demystify SwiftUI
Advantages of SwiftUI
High level API
The SwiftUI API is very simple, due to its declarative nature (Text, Image, VStack, ...).
The abstraction is enough for Apple to offer us compatibility with all its platforms. One interface for all, where it would have been necessary to adapt the UI between UIKit and AppKit for example.
Its simplicity and its proximity to other frameworks (React, Flutter, Jetpack Compose) are an open door for developers of other platforms to try native development on Apple platforms.
Interoperability
Interoperability is one of the major strengths of SwiftUI. Its use can be progressive, because it interfaces perfectly with UIKit and AppKit:
- it is possible to add SwiftUI code in an existing app (via UIHostingController),
- or to add UIKit code in a new app with declarative interface (via UIViewRepresentable), to compensate for the current limits of this young framework.
Interactive Canvas
Its deep integration with Xcode makes it a great asset. Thanks to the Previews, it is possible to directly visualize the rendering of a SwiftUI view. Any change in the code immediately reloads the view, which can be interacted with. Even better, it is possible to have a simultaneous rendering on different screen configurations.
Like Storyboards, Xcode Previews is an editor where you can drag and drop interface elements (Text, Button, ...). The major difference is that the Swift code is directly modified; no more unreadable .xml files.
The disadvantages of SwiftUI
A mobile framework that is still very young
Being a very recent framework, SwiftUI still has a lot of room for improvement. When version 1.0 was released, some people thought it was perhaps too early. Apple must have wanted to confront its community of developers in order to make it evolve as best as possible according to the feedback. Considered "production ready" since 2.0, it still lacks the same possibilities as UIKit/AppKit.
The community - mainly that of iOS - is consequent, but the content proposed on the internet being relatively recent, it may be that the questions we ask are currently unanswered.
Its infrastructure based on UIKit & AppKit
Underneath the beautiful APIs of SwiftUI are components implemented with UIKit/AppKit. If we can imagine that the future is bright for SwiftUI and that it will end up completely detached from its predecessors, today it relies entirely on it.
Of course, it benefits from their solid foundations, which allows for interoperability and rapid adaptation. However, this defines a framework that is too limited compared to the immense possibilities of the frameworks on which it was built.
Going beyond the "simple" cases provided by Apple always leads us to the limits of the framework. Not all UIKit/AppKit APIs have been ported to SwiftUI yet, and building a complex app without using UIKit directly is currently not possible.
Our experience with the SwiftUI framework
We were a bit reluctant at the beginning and waited a while before taking the plunge:
- as when Swift was launched, version 1.0 was still too young to be considered really usable.
- We didn't want to restrict the apps to the latest iOS version available. Working today with SwiftUI 2.0 allows us to have a minimum deployment target at iOS 14.
The joys we appreciate
The benefits of SwiftUI are felt instantly.
Creating a new view declaratively is a much more pleasant experience than defining sets of constraints to place our elements.
As users of reactive programming for a while now, relying on a UI framework designed for that is intuitive and pleasant.
Instantly seeing the result of interface modifications saves us precious time and reminds us of one of the advantages of web programming.
The interoperability allows us to choose, according to our needs, which framework to use to create our interface.
The frustrations we encountered
Everyone will point to the same problem: the limits of the framework.
SwiftUI, even if it is usable, is not yet complete. How many times have we found ourselves having to go through UIKit after realizing that SwiftUI would not allow us to complete our interface design? This backtracking is time-consuming and particularly frustrating because we had waited for the project to mature before using it.
Many things that are easy to implement with UIKit are more tedious with SwiftUI. Here are some examples:
- To access the contentOffset of a paged UIScrollView, one must use the now classic Color.clear - GeometryReader - PreferenceKey solution. Contrary to the "Less code" principle.
- Displaying a carousel, with a preview of the next element, is easy to do via a UICollectionView. No peek available with the TabView of SwiftUI. Fortunately the community has provided a solution with SwiftUI-Pager.
- The navigation management has been closed. Goodbye to modifying the navigation stack on the fly and displaying modals as we please. From now on, you have to respect the navigation logic to the letter, be patient and think about the context during development (which may imply duplicating code if you want to manage the display of global alerts regardless of the displayed modal, or else you'll get errors like "Attempt to present X on Y which is already presenting Z ")
- The status bar has long been a source of problems. They have been greatly reduced thanks to the use of preferredStatusBarStyle with UIKit. However, with SwiftUI, it has become more complicated to manage its color change. We take a step backwards to get around the limitations, as we have to use a deprecated API.
Overall, many of the API limitations can be compensated for by using the SwiftUI-Introspect open source library which allows access to the underlying UIKit components.
Conclusion
SwiftUI represents the future of development on all Apple platforms. But also the present thanks to the following points:
- Its strong interoperability with UIKit and AppKit allows a progressive installation in our apps.
- It should not be ignored, and may already be necessary (eg for iOS widgets).
- Today backward compatibility is often no longer a problem (iOS 14 min for 2.0 while iOS 16 will soon be announced)
- Its paradigm, with its declarative interface creation and its compatibility with reactive programming, makes it a modern and very pleasant framework to use.
However, making a quality app today that is 100% SwiftUI is almost impossible, depending on the complexity of the interfaces. The navigation, even though it uses existing codes (navigation stack, presentation sheets), is too closed. The creation of complex views is hampered by its current limits.
Perhaps we should finally move towards a UIKit Navigation / SwiftUI Views architecture to get the best of both worlds?