简体   繁体   中英

How to animate transition between views in SwiftUI?

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
    }
}

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)

演示

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)!
    }
}

演示2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM