对导航栏标题下方的视图进行动画

移动开发 2026-07-09

我正在构建一个Toast消息。本质上它只是一个矩形视图,我想在当前屏幕的顶部、导航栏下方显示几秒钟。还有一个要求是这个动画要从顶部开始。到现在为止还算简单。

 ToastView().transition(.move(edge: .top))

值得一提的是,我需要这个Toast的屏幕处于一个带有标题的导航视图(Navigation View)内部。当我将Toast放在视图中而不添加任何动画时,它就安放在导航栏下方,位置正合适。当我添加动画时,它会从屏幕顶部移动到正确的位置,但它会越过(也就是在导航栏之上)导航栏。

我希望得到完全相同的行为,但在Toast动画时,我希望它从导航栏下方进入(也就是从后方穿过)。

我的直觉是需要调整视图的z 索引,即确保NavBar的 z索引高于ToastView,但这并没有带来区别。欢迎给出任何建议。

解决方案

你需要将SwiftUI的 Toast视图放在导航栈内容之下,如下所示:

struct StackExample: View {
    @State private var showToast = false

    var body: some View {
        NavigationStack {
            ZStack(alignment: .top) {

                // Main content
                List {
                    Button {
                        withAnimation {
                            showToast.toggle()
                        }

                        if (showToast) {
                            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                                withAnimation {
                                    showToast = false
                                }
                            }
                        }
                    } label: {
                        Text("Item")
                    }
                }

                // Toast under NavBar
                if showToast {
                    ToastView()
                        .padding(.top, 8)
                        .transition(.opacity.combined(with: .offset(y: -40)))
                }
            }
            .navigationTitle("Stack Example")
        }
    }
}

备选方案

你可以尝试在主内容的背景中显示Toast视图。

你还提到:

还有一个要求是这个动画要从顶部开始。

过渡 .move(edge: .top) 的起始偏移量等于将要显示的内容的高度。如果你需要比这更大的移动,例如你希望从安全区域边界之外开始,只需通过增加内边距为内容提供更高的高度。

下面是一个示例,展示全部功能正常工作:

struct ContentView: View {
    @State private var isShowing = false

    var body: some View {
        NavigationStack {
            Text("Main content")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .navigationTitle("Title")
                .background(alignment: .top) {
                    if isShowing {
                        toastView
                            .transition(.asymmetric(
                                insertion: .move(edge: .top),
                                removal: .opacity
                            ))
                    }
                }
                .task {
                    try? await Task.sleep(for: .seconds(1))
                    withAnimation(.easeOut(duration: 3)) {
                        isShowing = true
                    } completion: {
                        withAnimation(.easeInOut.delay(3)) {
                            isShowing = false
                        }
                    }
                }
        }
    }

    private var toastView: some View {
        RoundedRectangle(cornerRadius: 10)
            .fill(.orange)
            .padding(.horizontal)
            .frame(maxWidth: .infinity)
            .frame(height: 50)
            .padding(.bottom, 250)
    }
}

动画

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章