Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom segue animation (navigation animation) in SwiftUI

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.

like image 839
matteopuc Avatar asked Aug 24 '19 16:08

matteopuc


People also ask

How do I add animations to SwiftUI?

SwiftUI has built-in support for animations with its animation() modifier. To use this modifier, place it after any other modifiers for your views, tell it what kind of animation you want, and also make sure you attach it to a particular value so the animation triggers only when that specific value changes.

What is withAnimation SwiftUI?

withAnimation() takes a parameter specifying the kind of animation you want, so you could create a three-second linear animation like this: withAnimation(.linear(duration: 3)) Explicit animations are often helpful because they cause every affected view to animate, not just those that have implicit animations attached.

How do I use navigation in SwiftUI?

Before NavigationStack and NavigationSplitView , SwiftUI introduced the NavigationView and NavigationLink structs. These two pieces work together to allow navigation between views in your application. The NavigationView is used to wrap the content of your views, setting them up for subsequent navigation.

How do I animate a view in SwiftUI?

If you want a SwiftUI view to start animating as soon as it appears, you should use the onAppear() modifier to attach an animation.


1 Answers

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

like image 54
matteopuc Avatar answered Oct 19 '22 10:10

matteopuc