9.
Getting Ready for SwiftUI
Written by René Cacheaux
More than a framework, SwiftUI is a new paradigm for creating apps in the Apple ecosystem. These are exciting times. We’re in the middle of a journey towards a post Cocoa, Swift-native world. Until now, you didn’t need to diverge from Apple’s Cocoa Objective-C based patterns and Model-View-Controller, MVC, architecture. This was true even if you started to write UIKit apps using Swift. SwiftUI brings a new set of building blocks, paradigms and patterns that are native to Swift. These new tools will help you, and other developers, be drastically more productive. And, more productive developers means better apps for users.
Note: This chapter assumes familiarity with SwiftUI and Combine concepts and terminology. To get hands on practice with SwiftUI and Combine, check out SwiftUI by Tutorials and Combine: Asynchronous Programming with Swift raywenderlich.com books.
This chapter will help you get your app’s architecture ready for SwiftUI. You’ll explore what SwiftUI means for app architecture and how SwiftUI is different than UIKit. You’ll also get some advice for practicing, preparing and planning SwiftUI integration. You’ll walk through things you can change in your codebase today, even if you don’t plan on integrating SwiftUI for a while. After reading this chapter, you’ll be able to decide how and when you should start incorporating SwiftUI into your existing iOS apps.
What SwiftUI means for app architecture
Architecting features with SwiftUI is new, fun and exciting. SwiftUI brings new powerful patterns and tools for you to use when architecting the Swift code behind your app’s UI. You end up writing less architectural boilerplate and more domain specific logic. And, you get a lot of behaviors, such as Publisher subscription, for free. SwiftUI enables you to break down your UI into small and reusable pieces that are much lighter than UIViewController. You can decompose large portions of UI code into small encapsulated components without paying a performance penalty. All to say, SwiftUI helps you build well architected UI systems.
Note: SwiftUI is quite new, so establishing architecture best practices will take some time. The content in this edition is based on initial explorations with SwiftUI. Advice and best practices will evolve over time. I recommend keeping an eye on SwiftUI content written by developers that have had a chance to ship production apps using SwiftUI.
The architectural patterns that SwiftUI enables are based on some of the industry’s latest best practices and paradigms. This is great news, especially if you’ve already invested time in learning any of these paradigms such as functional reactive programming. You can now use these paradigms without fighting the UI framework. If you’ve ever tried applying non-Apple paradigms to UIKit based iOS apps, you know how difficult it is to program against the framework. With SwiftUI, you won’t be fighting the UI framework when applying the latest thinking in UI architecture.
SwiftUI goes even further than the industry’s latest approaches. SwiftUI was clearly designed with UI architecture in mind. For example, view dependencies in SwiftUI are explicit. This makes architecting for SwiftUI incredibly satisfying because you can easily see all of a view’s dependencies when opening a SwiftUI file.
Today, SwiftUI is not a complete application framework. Currently, SwiftUI does not have application level APIs. Because of this, even when using SwiftUI, you’re still architecting within the context of UIKit. You still use a UIApplicationDelegate and you still need to give your root UIWindow a root UIViewController. This means if you want to use SwiftUI, you’ll be building mixed UIKit and SwiftUI apps for the foreseeable future. Fortunately, incorporating SwiftUI into a UIKit architecture is incredibly easy. This is awesome because you’ll be able to gradually adopt SwiftUI over time.
Because most developers who are interested in using SwiftUI will be working from an existing UIKit iOS app, this chapter focuses on how to get an existing app’s architecture ready for SwiftUI.
Architecting with SwiftUI versus UIKit
SwiftUI expects and enables a different kind of architecture than UIKit. UIKit favors an Objective-C object-oriented imperative MVC approach where every type is a reference and every view is mutable. This is different from SwiftUI’s reactive functional approach where immutable value types are the norm. Architecting features using SwiftUI might feel strange at first. Don’t let this discourage you! Once you get the hang of it, architecting with SwiftUI is delightful. You’ll be able to build features much faster using SwiftUI compared to UIKit.
SwiftUI brings a brand new building block for you to architect with. UIViewController is UIKit‘s main building block whereas View is SwiftUI’s main building block. Both UIViewController and View are incredibly compose-able. However, View is much lighter than UIViewController. Breaking down large SwiftUI views into small views is one of the main architectural skillsets you’ll need to build well architected SwiftUI features.
While UIKit is Objective-C native, SwiftUI is Swift native. UIKit is built on Objective-C patterns such as target-action, delegation, data source and others. SwiftUI is built on Swift principles such as immutable value types and functions. Because of this, depending on how familiar you are with using practices native to Swift, SwiftUI might feel foreign.
The SwiftUI system behaves completely differently than UIKit‘s system. For example, when learning SwiftUI, I found that architecting with views as value types to be difficult at first. Not everything can be a value type, so I found myself needing to figure out how to keep reference type objects alive using SwiftUI value-type views. This is challenging because, unlike UIKit UIView objects, SwiftUI View values get destroyed and recreated every time an ancestor view’s state changes. Therefore, in order to keep a reference type object alive for the right amount of time, you need to have a thorough understanding of when your app’s SwiftUI views get recreated based on your app’s state-change logic. This is just one example of how you might need to build new intuitions to avoid getting surprised by how SwiftUI behaves.
What about architectural patterns? UIKit‘s architecture of choice is MVC. SwiftUI’s architecture of choice is a mix between React.js-State and Model-View-ViewModel, MVVM. There’s really no single architecture pattern that fully describes SwiftUI. You could say that SwiftUI uses its own architecture.
When to start building with SwiftUI
SwiftUI is available on iOS 13 and above. Therefore, in order to use SwiftUI you’ll need to require your users to upgrade to iOS 13. Or, you’ll need to build the same features in both UIKit and SwiftUI so that you can ship your app to pre-iOS 13 devices. You can use #available and @available to fork control flow depending on SwiftUI’s availability. Because SwiftUI requires iOS 13, most teams won’t adopt SwiftUI extensively until the majority of their user base has upgraded to iOS 13.
Also keep in mind that SwiftUI is in its infancy. There’s a lot of UIKit functionality that’s not available in SwiftUI yet. The missing functionality, alone, shouldn’t keep you from using SwiftUI. However, because SwiftUI requires iOS 13 and because SwiftUI is a brand new framework, it’s not a bad idea to wait until iOS 14 to begin using SwiftUI extensively in your existing iOS apps. If you’re building a new app from scratch, consider yourself lucky! There’s no reason not to use SwiftUI, unless your target audience is slow to upgrade their iDevices.
At the time of writing, most of us are at least one year away from being able to use SwiftUI extensively. But, that doesn’t mean you can’t practice, plan and prepare in the meantime. The rest of the chapter focuses on what you can to today to get your app’s architecture ready for SwiftUI.
Practicing
Because architecting with SwiftUI is so different than architecting with UIKit, it’s worth experimenting and practicing with SwiftUI in order to become familiar with SwiftUI’s mechanics. This section covers some of the architecture skills you can practice to gain that familiarity.
Decomposing views
First, practice breaking down large SwiftUI views into smaller reusable views. Take an existing screen in one of your apps and build the UI using SwiftUI. Build the screen in one SwiftUI View. Don’t worry about hooking your practice SwiftUI view into networking and persistence subsystems. Use @State for any mutable UI state. And, for your first practice views, avoid @ObservedObject and @EnvironmentObject because they add unnecessary complexity that isn’t critical to learning the architectural foundations of SwiftUI.
After you’ve built the screen using a single View, you can start breaking down the screen into smaller and smaller custom subviews. By doing this you’ll get a feel for threading mutable state down a view hierarchy via @Bindings. If your practice screen handles any user interaction, you’ll also learn how to design custom views that perform actions.
Understanding the view lifecycle
Once you’re comfortable breaking views down, you can get familiar with the View lifecycle, i.e. when View struct values are created, destroyed and recreated. A great way to do this is to place log statements in View initializers and in var body computed property closures. Do this at all levels of the view hierarchy and notice what views get re-created in response to state changes. This will help you understand which views are best suited for holding onto references to longer lived objects such as dependency containers.
After exploring the View lifecycle, you can start getting a feel for how SwiftUI correlates different View values together. Look for a view that gets recreated often and add an onAppear modifier with a closure that logs output. You might be surprised that SwiftUI calls the onAppear closure only once, even though the view gets recreated over and over again. SwiftUI knows that all of these View values correlate to the same underlying view that is already on screen. Therefore, SwiftUI doesn’t call onAppear again. In practice, this means you can run logic in onAppear to load data for a view. There are other lifecycle modifiers, like onDisapper, you can use to run logic at other points in time.
Connecting UIKit to SwiftUI
It’s also worth exploring how to bridge between UIKit and SwiftUI. You can practice incorporating SwiftUI views through UIViewController presentation APIs and through UIViewController containment APIs. Try creating and providing an @ObservableObject to a UIHostingController’s SwiftUI View. You can also practice injecting @Environment values and @EnvironmentObjects by calling modifiers on a UIHostingController’s SwiftUI View. This will help you build intuition for how to provide values and objects from UIKit into SwiftUI.
These are just a couple of ways you can practice architecting for SwiftUI. Once you’ve built up enough intuition to feel confident, you can start preparing your codebase for SwiftUI.
Preparing
Even if you’re not ready to ship features built with SwiftUI, there are changes you can make today to your existing codebase to prepare for SwiftUI. Here are some ideas.
Migrating to value types
Take a look at your app’s data model types. SwiftUI works best when data models are designed as value types. As a matter of fact, @State can only store values. If your data models are designed as reference types, consider refactoring them into struct or enum types.
Moving state to state containers
While data model types themselves should be value types, you’ll most likely need a state container to hold values. State containers are just objects with value type properties. In UIKit, you can use a UIViewController, a view model or a Redux store as a state container. Once you’re ready to start building features with SwiftUI, you’ll need to decide whether you want to store your data model values in a @State container or in an @ObservableObject container. Refactoring your data model types to value types will help you easily use your existing data model within SwiftUI.
Take a look at your view controllers and see if they are currently holding onto the data model objects that are used to render views. If they are, you’re probably using MVC. If this is the case, you can refactor your view controller state management logic into a view model object per MVVM. The idea is to design view model objects that can be easily converted to @ObservableObjects. If you’re already using MVVM, you can look at your view models and asses how easily they can be turned into @ObservableObjects and make any necessary modifications.
These are two steps you can take today to prepare your codebase for transitioning from UIKit to SwiftUI. That covers practicing and preparing for SwiftUI. Next, you’ll explore some ideas for how to begin making your SwiftUI transition plan.
Planning
You’ll most likely be gradually adopting SwiftUI into your current codebase. You might not have enough time to ease SwiftUI integration by making broad sweeping changes across your entire codebase. In addition, SwiftUI does not provide all the functionality available in UIKit. So it’s a good idea to plan your codebase’s SwiftUI adoption ahead of time.
The first step is to determine where to incorporate SwiftUI. Since you can incorporate SwiftUI anywhere that you can incorporate a UIViewController, you can adopt SwiftUI virtually anywhere in your iOS codebase. So how do you decide where to start? Here are some tips that can help guide you through this decision.
Starting with new screens
When looking for things to build using SwiftUI, consider building new screens or components. You can incorporate SwiftUI components into existing view controllers via UIViewController containment APIs and you can incorporate SwiftUI screens via UIViewController presentation APIs. It’s a good idea to start here because building a single screen or component limits the amount of concepts you’ll need to be familiar with to build a well architected SwiftUI feature. You won’t have to design for things like navigation and dependency scopes. The lifetime of a screen or component is relatively easy to manage compared to a SwiftUI View that can present and dismiss many screens.
You might need to inject existing objects from your existing codebase. However, try to build as much of the SwiftUI functionality from scratch. Especially view state management, i.e. logic commonly found in an MVVM’s view model. Building an isolated component or screen from scratch will help you get a feel for what a SwiftUI native architecture should look like. It will also prevent your existing UIKit code from influencing how you build intuition for SwiftUI architecture.
Simple and immutable screens and components are the easiest to build. They are the best candidates for a first SwiftUI feature. If the screen or component you’re considering building with SwiftUI needs to respond to changes in state, you’ll need to be able to observe those changes with some sort of Combine Publisher. You can easily observe changes if you’re using Apple provided iOS technologies such as Core Data, User Defaults and Notification Center. Incorporating observability could take more effort if you’re using custom technologies. Some things to think about as you plan your first couple of SwiftUI features.
Selecting a state management strategy
Once you know what to build with SwiftUI, you can start thinking about state management. If your SwiftUI screen or component is immutable and does not need to observe changes in state, you don’t need to worry about state management. You can simply use properties on the root View and subviews to hold onto data model values. Or, if the data is global, you can extend EnvironmentValues and provide the values as @Environment values. Make sure to only do this for truly global values because any SwiftUI View will be able to request any custom values added to EnvironmentValues. If you do need to manage mutable state, you can use @State, @ObservedObject and @EnvironmentObject.
@State is slightly easier to use because you don’t have to manage the lifetime of the @State container object. SwiftUI automatically keeps @State containers alive across View recreations. @State is it’s own architectural pattern that’s very similar to React.js’s state architecture.
With @ObservedObject, you’ll need to figure out how to keep the associated @ObservableObject alive. SwiftUI does not manage the lifetime of an @ObservedObject. Your root SwiftUI View is most likely the best place to hold an @ObservableObject. You can inject @ObservableObjects when initializing the root SwiftUI View. @ObservedObject is essentially a view model. You can think of @ObservedObject as following the MVVM architectural pattern.
You can also inject an @ObservableObject as an @EnvironmentObject by modifying the root SwiftUI View with the environmentObject modifier. This is easy but also risky because there’s no compile time check. If, at some point later, someone accidentally removes the environmentObject modifier, SwiftUI will trap when SwiftUI can’t find an @EnvironmentObject instance. @EnvironmentObject is best suited for injecting dependencies that a majority of your views, in your SwiftUI view hierarchy need. @EnvironmentObject is also useful when you need to provide different instances of a dependency to different subview-hierarchies. Therefore, @EnvironmentObject is best suited for SwiftUI features with many screens with deep view hierarchies. This is the kind of complexity you can avoid at first by starting your SwiftUI journey with single screens and components.
Migrating existing screens
If you’re done shipping new screens or if you don’t have anything new to build, you can start planning how to migrate existing screens from UIKit to SwiftUI. But first, you might be wondering whether you should re-write existing UIKit code rather than plugging your existing UIViewControllers and UIViews into SwiftUI via UIViewRepresentable. This depends on your migration strategy. You could either take a bottom up approach or a top down approach.
In a top down approach, you start converting your root-most UIKit logic to SwiftUI. In this scenario, you would make heavy use of UIViewRepresentable to plug in existing view controllers into the top layer built in SwiftUI. This approach is more complex than bottom up because you have to design for things like navigation and dependency scopes. It’s definitely a valid approach. Expect to hit surprises though. You can think of this approach as the more aggressive one. If you’d like to go through the SwiftUI transition as quickly as possible, this is the best way.
In a bottom up approach, you continue to frame your app using UIKit. Your app presents view controllers that are either written in UIKit or SwiftUI. This is a more natural progression because SwiftUI naturally fits in this way already. Even if you’re using SwiftUI exclusively, your app runs as a UIKit UIApplication. This is a slower but steadier approach. This approach gives you plenty of time to see what best practices other developers discover and gives SwiftUI time to mature a bit more. If you think of your app as a graph of view controllers, this migration approach works by migrating the leaf nodes first and up towards the graph’s root view controller. You start by building a bunch of content screens in SwiftUI and then work your way up to container-navigation views and up to the window’s root view controller. You’ll get a chance to explore some of the more complex SwiftUI features such as navigation. But, you’ll get to explore this within a much smaller isolated scope compared to the top down approach. If you’re not in a rush to migrate to SwiftUI, this is the best approach.
Regardless of which approach you take, you’ll want to first understand where your UI state’s source of truth lives today. Most likely, your persistence layer is your ultimate source of truth. However, your UI state’s source of truth is most likely in a UIViewController or held by UIView properties. It’s helpful to look at your code and understand this because SwiftUI expects you to architect sources of truth into a SwiftUI view hierarchy. Taking stock of where your state lives today will help you understand what data you should place in @State or in an @ObservableObject.
You should expect a full SwiftUI migration to take several years, depending on how much UIKit code exists in your codebase today. We’re just at the beginning of a long but exciting journey into Apple’s next generation of developer productivity tooling. I hope you’re as excited as I am for the future of application architecture on Apple platforms.
Key points
- SwiftUI is available starting on iOS 13 and it requires a deployment target of iOS 13 or above.
- Even when using SwiftUI, you’re still architecting within the context of
UIKit. - Because architecting with SwiftUI is so different than architecting with
UIKit, it’s worth experimenting and practicing with SwiftUI in order to become familiar with its mechanics. - Even if you’re not ready to ship features built with SwiftUI, there are changes you can make today to your existing codebase to prepare for the future, such as migrating to value types and moving state to state containers.
- You’ll most likely be gradually adopting SwiftUI into your current codebase.
- When looking for things to build using SwiftUI, consider building new screens or components.
- You can take a bottom up or top down approach to migrating an app from
UIKitto SwiftUI. - If you can’t wait and would like to see some SwiftUI architecture code you can check out the source on Github at https://github.com/raywenderlich/swiftui-example-app-koober.
Where to go from here?
That wraps up getting started with SwiftUI architecture. But, there’s one more thing… We are in the process of rebuilding this book’s example app, Koober, entirely in SwiftUI. If you can’t wait and would like to see some SwiftUI architecture code you can check out the source on Github at https://github.com/raywenderlich/swiftui-example-app-koober. The example is not finished at the time of writing. However, you can follow along as we work towards completing the examples.
Now is a great time to get your hands on SwiftUI. The internet is exploding with great SwiftUI and Combine resources. There’s a lot of excitement and anticipation. Here are some helpful raywenderlich.com resources you can use to learn more about SwiftUI and Combine:
- Tutorials. Our site has a ton of awesome free SwiftUI and Combine tutorials. Visit and search for SwiftUI or Combine to see what’s available.
- Screencasts. If you learn best with videos, the site also has many SwiftUI and Combine screencasts available with a subscription.
- Books. If you enjoy our book tutorial format, take a look at SwiftUI by Tutorials and Combine: Asynchronous Programming with Swift.
I hope you enjoy getting familiar with SwiftUI! Also, we’d love to hear from you. If you have any questions or comments, don’t hesitate to drop us a line in our book’s forum.