How SwiftUI can now be used to build entire iOS apps
Up until this year’s release, apps built using SwiftUI have still needed to use mechanisms from Apple’s previous UI frameworks, UIKit and AppKit, to implement their entry points — such as using UIApplicationDelegate
to define an app delegate for an iPhone or iPad app.
This year, however, entire apps can now be defined directly using SwiftUI, thanks to a few new additions to its API. For example, let’s say that we’ve been building a podcast app, which uses a TabView
as its root view, which then contains three tabs — a library view, a discover view, and a search view:
struct RootView: View {
var body: some View {
TabView {
LibraryView()
DiscoverView()
SearchView()
}
}
}
On iOS 13 and its sibling OSs from last year, we then had to use a UIHostingController
(or NSHostingController
on the Mac) to actually render the above view, for example by assigning it as the rootViewController
of a UIWindow
. But now, the above root view hierarchy can simply be embedded in a type conforming to the new App
protocol, and by annotating that type with Swift’s new @main
attribute, it’ll act as the entry point for our app — without the need for any app delegate or any other bootstrapping code:
@main struct PodcastApp: App {
var body: some Scene {
WindowGroup {
TabView {
LibraryView()
DiscoverView()
SearchView()
}
}
}
}
The above WindowGroup
type is a built-in implementation of another new protocol, Scene
, which is a native SwiftUI equivalent to the UIScene
API that was introduced last year — primarily in order for iPad apps to gain multi-window support.
While we can also create our own custom Scene
types, if we’re looking to always render the same view hierarchy within all of our app’s scenes, then simply using WindowGroup
is a great option.
But the cool thing is that, since SwiftUI is so composable, even if we choose to build our own Scene
, we can still use WindowGroup
to implement its body
, while also providing our own custom logic as well.
For example, here we’ve built a custom scene for our podcast app, which uses the new scenePhase
environment value to observe whenever the overall phase of our scene changed — for example to detect when it was moved from an active to inactive state:
struct PodcastScene: Scene {
@Environment(\.scenePhase) private var phase
var body: some Scene {
WindowGroup {
TabView {
LibraryView()
DiscoverView()
SearchView()
}
}
.onChange(of: phase) { newPhase in
switch newPhase {
case .active:
// App became active
case .inactive:
// App became inactive
case .background:
// App is running in the background
@unknown default:
// Fallback for future cases
}
}
}
}
While the new App
and Scene
protocols currently don’t offer the same amount of functionality and flexibility as their UIKit and AppKit equivalents, the fact that some apps will now be able to use a 100% SwiftUI-based implementation is incredibly cool — and a big step forward for SwiftUI as a framework.
We’ll take a much closer look at what’s new in SwiftUI, as well as how the above new way of defining apps also enables document-based Mac apps to be created in an astonishingly lightweight way, over the next few days.
Thanks for reading! 🚀
Fast and rock-solid Continuous Integration. Automatically build, test and distribute your app on every Pull Request — which is perfect for teams that are now working remotely, as you’ll quickly get feedback on each change that you make. Try out their new, improved free tier to get started.