在SwiftUI中如何实现视图之间的动画过渡?

19

我持有以下观点。

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

我想对子视图之间的切换进行动画处理。 我看到一些使用一个状态来进行切换的示例,但我需要使用多个状态。 我还尝试在子视图内使用 onAppearonDisappear 来应用动画。这是可行的,但当视图不再显示时,onDisappear 就不再执行了。此外,我希望它能适用于全部视图。

有没有办法在不使用多种状态的情况下做到这一点?我想只使用 .transition.animation

目前,我认为最好的解决方案是这样的。

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

每个视图切换器上。

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

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
27
使用以下方法,您可以根据需要修改appMode(onAppear、onTapGesture等)。 当然,动画持续时间可以设置为任何您希望的时间(实际上,转换类型也可以设置为任何您希望的类型,但是某些转换在预览中表现不佳,应在模拟器或实际设备上进行测试)。 演示:(第一个闪烁只是预览启动,然后是两个过渡用于onAppear,然后是用于onTap的过渡) demo 在Xcode 11.4 / iOS 13.4上测试 - 可以在预览和模拟器上运行。 代码:重要部分内联标注。
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)))
        }
    }
}

更新:对于.slide(和可能的其他移动转换),状态变化应该包含在明确的withAnimation之内,像下面这样:

withAnimation {
    self.appStore.appMode = new_mode_here
}
注意:这是一种预览不支持的转换方式 - 请在模拟器中测试。 示例,比如转换方式:
    ...
    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


1
谢谢,它完美地工作了。然而,我玩了一下代码,有两个新问题。1。为什么它不适用于“slide”动画?2。为什么我不能省略“AnyTransition”? - cakeheart
6
如果它对你来说“完美运作”,我可以问一下为什么你没有接受这个答案呢? - Asperi
7
我还在理解这个答案的中间阶段。但是,你只是想要这些分数。给你。 - cakeheart
@Asperi 是的,为什么使用.slide动画不起作用呢?.scale.opacity效果完美。你知道原因吗? - acmpo6ou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,