22

I have the following view.

import SwiftUI

struct AppView: View {
    @EnvironmentObject var appStore: AppStore

    var body: some View {
        ZStack {
            Color.blue

            if .game == self.appStore.appMode {
                GameView()
            } else if .options == self.appStore.appMode {
                OptionsView()
            } else {
                MenuView()
            }
        }
    }
}

I want to animate the switching between the child views. I have seen some examples using a state for one switch, but I'm relying on more than one. I also tried to apply the animation inside a child view with onAppear and onDisappear. That works, but when the view is not shown, the onDisappear does not get executed anymore. Besides, I would like to make it work for all the views.

Is there any way to do this without using multiple states? I would love to use only .transition and .animation.

Right now my best solution is this.

import SwiftUI

struct AppView: View {
    @EnvironmentObject var appStore: AppStore

    var body: some View {
        ZStack {
            Color.blue

            if .game == self.appStore.appMode {
                GameView()
                .transition(.scale)
                .zIndex(1) // to keep the views on top, however this needs to be changed when the active child view changes.
            } else if .options == self.appStore.appMode {
                OptionsView()
                .transition(.scale)
                .zIndex(2)
            } else {
                MenuView()
                .transition(.scale)
                .zIndex(3)
            }
        }
    }
}

And on every view changer.

.onTapGesture {
    withAnimation(.easeInOut(duration: 2)) {
        self.appStore.appMode = .game
    }
}

1 Answer 1

28

With the following approach you can modify your appMode as you wish (onAppear, onTapGesture, etc.). Animation duration is, of course, can be set any you wish (as well as kind of transition, actually, however some transitions behaves bad in Preview, and should be tested on Simulator or real device).

Demo: (first blink is just Preview launch, then two transitions for onAppear, then for onTap)

demo

Tested with Xcode 11.4 / iOS 13.4 - works and in Preview and in Simulator.

Code: Important parts marked with comments inline.

var body: some View {
    ZStack {
        // !! to keep background deepest, `cause it affects removing transition
        Color.blue.zIndex(-1)

        // !! keep any view in explicit own `if` (don't use `else`)
        if .game == self.appStore.appMode {
            GameView()
                .transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
        }

        if .options == self.appStore.appMode {
            OptionsView()
                .transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
        }

        if .menu == self.appStore.appMode {
            MenuView()
                .transition(AnyTransition.scale.animation(.easeInOut(duration: 1)))
        }
    }
}

Update: for .slide (and probably other moving transitions) the change of state should be wrapped into explicit withAnimation, like below

withAnimation {
    self.appStore.appMode = new_mode_here
}

Note: these is one of transitions that is not supported by Preview - test in Simulator.

Example for transition like

    ...
    if .menu == self.appStore.appMode {
        Text("MenuView").frame(width: 300, height: 100).background(Color.red)
            .transition(AnyTransition.move(edge: .bottom).combined(with: .opacity).animation(.easeInOut(duration: 1)))
    }
}
.onTapGesture {
    withAnimation {
        let next = self.appStore.appMode.rawValue + 1
        self.appStore.appMode = next > 2 ? .game : AppStore.AppMode(rawValue: next)!
    }
}

demo2

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you, it works perfectly. However, I played around with the code and I have two new questions. 1. Why does it not work with the .slide animation? 2. Why can't I leave out the "AnyTransition"?
If it works perfectly for you, can I ask why you did not accept answer?
I am still in the middle of understanding the answer. But, you're after the points and that is it. Here you go.
@Asperi Yes, why doesn't it work with the .slide animation? .scale and .opacity work perfectly. Do you know why?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.