简体   繁体   English

如何在 SwiftUI 中为视图之间的过渡设置动画?

[英]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 .我还尝试使用onAppearonDisappear在子视图中应用动画。 That works, but when the view is not shown, the onDisappear does not get executed anymore.这有效,但是当视图未显示时, onDisappear不再执行。 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 .我喜欢只使用.transition.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.).使用以下方法,您可以根据需要修改您的appMode (onAppear、onTapGesture 等)。 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)演示:(第一次闪烁只是预览启动,然后是 onAppear 的两个过渡,然后是 onTap)

演示

Tested with Xcode 11.4 / iOS 13.4 - works and in Preview and in Simulator.使用 Xcode 11.4 / iOS 13.4 进行测试 - 可以在预览版和模拟器中使用。

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更新:对于.slide (可能还有其他移动转换),状态的变化应该被包装成显式withAnimation ,如下所示

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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