Animating view transitions in SwiftUI

One building block for navigating from one view to another is NavigationView which is a representation of UINavigationController in UIKit. This time, let’s take a look on how to transition from one SwiftUI view to another one without NavigationView.

AppFlowCoordinator managing choosing the view

The idea is to have a root SwiftUI view with only responsibility of presenting the active view. State is stored in AppFlowCoordinator which can be accessed from other views and therefore other views can trigger navigation. Example case we’ll build, is animating transitions from login view to main view and back. As said, AppFlowCoordinator stores the information about which view should be on-screen at a given moment. All the views are represented with an enum and based on the value in enum, views are created. This coordinator is ObservableObject what makes it easy to bind to a SwiftUI view – whenever activeFlow changes, SwiftUI view is updated. The term flow is used because views can consist of stack of other views and therefore creating a flow of views.

import SwiftUI
final class AppFlowCoordinator: ObservableObject {
@Published var activeFlow: Flow = .login
func showLoginView() {
withAnimation {
activeFlow = .login
}
}
func showMainView() {
withAnimation {
activeFlow = .main
}
}
}
extension AppFlowCoordinator {
enum Flow {
case login, main
}
}
Triggering navigation using flow controller.

RootView displaying active flow

RootView selects which view is currently visible. It accesses coordinator through environment. SwiftUI requires EnvironmentObjects to be ObservableObjects, therefore this view is automatically refreshed when activeFlow changes in the AppFlowCoordinator. RootView’s body is annotated with @ViewBuilder which will enable the view to return a body with type depending on the current state (HStack is also a ViewBuilder). Other options are wrapping the views with AnyView or using Group. In our case the view types are LoginView and ContentView. Both views also define the transition animation what is used when view refresh is triggered in withAnimation closure in AppFlowCoordinator. Asymmetric enables defining different transitions when view is added and removed from the view hierarchy.

let appFlowCoordinator = AppFlowCoordinator()
let rootView = RootView().environmentObject(appFlowCoordinator)
window.rootViewController = UIHostingController(rootView: rootView)
view raw SceneDelegate.swift hosted with ❤ by GitHub
Inserting AppFlowCoordinator to environment
struct RootView: View {
@EnvironmentObject var appFlowCoordinator: AppFlowCoordinator
@ViewBuilder
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
if appFlowCoordinator.activeFlow == .main {
ContentView().transition(.asymmetric(insertion: .scale, removal: .opacity))
}
else if appFlowCoordinator.activeFlow == .login {
LoginView().transition(.asymmetric(insertion: .slide, removal: .opacity))
}
else {
EmptyView()
}
}
}
}
view raw RootView.swift hosted with ❤ by GitHub
Updating currently visible flow with transition animations

Triggering navigation from SwiftUI view

Last piece we need to take a look at is how to trigger transition. As AppFlowCoordinator is in environment, any view can access the coordinator and call any of the navigation methods. When login finishes, LoginView can tell the coordinator to show the main content view.

struct LoginView: View {
@EnvironmentObject var appFlowCoordinator: AppFlowCoordinator
var body: some View {
ZStack {
Button(action: appFlowCoordinator.showMainView) {
Text("Login")
}
}
}
}
view raw LoginView.swift hosted with ❤ by GitHub
Navigating to main view from login view

Summary

We took a look on how to navigate from one SwiftUI view to another by using a coordinator object. Coordinator stored the information about which view we should currently display on screen. We saw how easy it is to trigger navigation from any of the currently visible views.

transition animation
Low FPS GIF representing the transition animation

If this was helpful, please let me know on Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Example

RootViewTransitions Xcode 11.2.1, Swift 5.1

One Reply to “Animating view transitions in SwiftUI”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s