25

In SwiftUI we use NavigationView and NavigationLink views to perform navigations (what we used to call segue in UIKit). The standard segue in UIKit is the show segue. In SwiftUI we can simply do:

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Destination")) {
                    Text("Navigate!")
                }
            }
        }
    }
}

to have the exact same effect (even if the word segue has disappeared).

Sometimes (actually rather often) we need to customise the segue animation.

  • We can decide not to animate the segue at all, indeed in the storyboard we can find the attribute Animates (true/false) in the attribute inspector by clicking on a segue. This way the destination view controller appears immediately in place of the source view controller.
  • Or we can decide to perform a custom animation. Usually this is done by implementing an object that conforms to the UIViewControllerAnimatedTransitioning protocol. All the magic happens in the animateTransition method that gives us access to the source view controller and the destination view controller.

For example, a simple cross-fade segue animation could be something like:

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    UIView* containerView = [transitionContext containerView];
    UIViewController* fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController* toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    toVC.view.alpha = 0;
    [containerView addSubview:toVC.view];

    [UIView animateWithDuration:1 animations:^{
        toVC.view.alpha = 1;
        fromVC.view.alpha = 0;
    } completion:^(BOOL finished) {
        [fromVC.view removeFromSuperview];
        [transitionContext completeTransition:YES];
    }];
}

Now the question: how can I get the same in SwiftUI? Is it possible not to animate a navigation or to customise the navigation animation? I expected to be able to do:

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Destination")) {
                    Text("Navigate!")
                }
            }
        }
        .animation(nil)
    }
}

or something similar to prevent the animation (or to add a custom animation), but nothing changes.

superpuccio
  • 11,674
  • 8
  • 65
  • 93
  • There are transitions you can use and even combine but I haven't tried them myself. – Michael Salmon Aug 24 '19 at 19:50
  • I don't think SwiftUI has this built-in yet, but it would be possible to pull it off with some hacking. If I think of something later I'll post an answer – joehinkle11 Dec 02 '19 at 22:54
  • 1
    To disable the navigation animation check this answer: https://stackoverflow.com/a/59481362/8397245 – FRIDDAY Jan 11 '20 at 17:58
  • @FRIDDAY thanks, very nice workaround, I upvoted your answer. It would be great if we can find a way to customise the transition animation too. – superpuccio Jan 11 '20 at 20:25
  • custom animations exist in SwiftUI as well https://swiftui-lab.com/advanced-transitions/ – user3441734 Feb 01 '20 at 21:26
  • Hi @user3441734 I know that custom transitions exist in SwiftUI, but the question here is: how can I customise the NavigationView transitions? How can I apply a custom transition to a NavigationView transition? – superpuccio Feb 02 '20 at 01:23
  • to prevent animation, you can set View.id(:), https://stackoverflow.com/questions/59602045/list-reload-animation-glitches/60022550#60022550 – user3441734 Feb 02 '20 at 01:30

1 Answers1

11

Unfortunately it's still not possible to customise the NavigationView transition animations in SwiftUI (xCode 11.3.1).

Looking for a way to do that I ended up creating this open source project called swiftui-navigation-stack (https://github.com/biobeats/swiftui-navigation-stack) that contains the NavigationStackView, a view that mimics the navigation behaviours of the standard NavigationView adding some useful features. For example you can turn off the transition animations or you can customise them with the transition you prefer.

Months ago I asked here above how to turn off transition animations. To do that you can use the NavigationStackView this way:

struct ContentView : View {
    var body: some View {
        NavigationStackView(transitionType: .none) {
            ZStack {
                Color.yellow.edgesIgnoringSafeArea(.all)

                PushView(destination: View2()) {
                    Text("PUSH")
                }
            }
        }
    }
}

struct View2: View {
    var body: some View {
        ZStack {
            Color.green.edgesIgnoringSafeArea(.all)
            PopView {
                Text("POP")
            }
        }
    }
}

If you specify .none as transitionType you can turn off the animations. PushView and PopView are two views that allow you push and pop views (similar to the SwiftUI NavigationLink).

The result is:

enter image description here

If you, instead, want to customise the transition animation (as I asked in the question here above with the cross-fade example) you can do like this:

struct ContentView : View {
    let navigationTransition = AnyTransition.opacity.animation(.easeOut(duration: 2))

    var body: some View {
        NavigationStackView(transitionType: .custom(navigationTransition)) {
            ZStack {
                Color.yellow.edgesIgnoringSafeArea(.all)

                PushView(destination: View2()) {
                    Text("PUSH")
                }
            }
        }
    }
}

struct View2: View {
    var body: some View {
        ZStack {
            Color.green.edgesIgnoringSafeArea(.all)

            PopView {
                Text("POP")
            }
        }
    }
}

For a cross-fase transition I specified:

let navigationTransition = AnyTransition.opacity.animation(.easeOut(duration: 2))

a cross fade transition that lasts 2 seconds. Then I passed this transition to the NavigationStackView:

NavigationStackView(transitionType: .custom(navigationTransition))

enter image description here

superpuccio
  • 11,674
  • 8
  • 65
  • 93
  • Thanks for building this framework! Is there a way to navigate outside of the direct view? I am worried that my views will know too much about the app's navigation structure i.e. I would be tightly coupling views together. – Zorayr May 07 '20 at 03:16
  • Also, as a bonus point, would be nice if you could speak a bit about how to answer the question directly i.e. how to customize the animations w/out using your framework. – Zorayr May 07 '20 at 03:18
  • 1
    Hi @Zorayr! About your first question: in order to get what you want you have to change the framework. ATM the navigation stack is created by the NavigationStackView, so you can access it only inside a NavigationStackView. It's not that difficult to change this, though: you can let the NavigationStackView take a NavigationStack as parameter on creation. Then, you can create a NavigationStack wherever you want and inject it in the NavigationStackView. This way, you'll be able to navigate using the NavigationStack (e.g. `navStack.push(MyView())`) outside the NavigationStackView. – superpuccio May 07 '20 at 08:30
  • 1
    About the second comment: actually, as I said in my answer, there's no way to customise the animation related to a navigation performed with the standard NavigationView/NavigationLink. I'm pretty sure this feature will be introduced soon (maybe during the next WWDC?) but at the moment SwiftUI is still missing this thing. – superpuccio May 07 '20 at 08:34