You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI实现类iOS默认导航的ZStack视图过渡动画方案

解决SwiftUI实现类似iOS导航/地图Sheet的堆叠滑入滑出效果

问题根源

原代码仅在ZStack中渲染当前栈顶视图,无法形成视图堆叠结构,导致过渡动画只能触发简单的透明度变化,同时无法实现上层视图覆盖底层的效果。此外,非栈顶视图未被正确隐藏,会导致ultraThinMaterial背景叠加影响视觉效果。

解决方案

  1. 在ZStack中渲染栈内所有视图,通过zIndex控制层级,确保栈顶视图始终在最上层
  2. 对非栈顶视图设置完全透明(opacity(0)),避免背景材质叠加
  3. 为每个视图单独配置不对称过渡动画,实现从底部滑入/滑出的效果
  4. 利用id修饰符确保视图状态正确刷新,触发过渡动画

修改后的完整代码

enum ViewToShow: Identifiable {
    var id: ViewToShow { self }
    case view1, view2, view3
}

@Observable
class SheetStackModel {
    var viewsItemManagedByThisSheet: [ViewToShow] = []
    
    init() {
        pushIntoStack(view: .view1)
    }
    
    func getLastView() -> ViewToShow? {
        viewsItemManagedByThisSheet.last
    }
    
    func pushIntoStack(view: ViewToShow) {
        withAnimation(.default.speed(1.8)) {
            viewsItemManagedByThisSheet.append(view)
        }
    }
    
    func popOutOfStack() {
        withAnimation(.default.speed(1.8)) {
            _ = viewsItemManagedByThisSheet.removeLast()
        }
    }
}

struct View1: View {
    @Environment(SheetStackModel.self) var sheetStackModel
    
    var body: some View {
        VStack {
            Text("View 1")
            Button("Go to View 2") {
                sheetStackModel.pushIntoStack(view: .view2)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

struct View2: View {
    @Environment(SheetStackModel.self) var sheetStackModel
         
    var body: some View {
        VStack {
            Text("View 2")
            Button("Go back to View 1") {
                sheetStackModel.popOutOfStack()
            }
            Button("Go to Detail 3") {
                sheetStackModel.pushIntoStack(view: .view3)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.ultraThinMaterial)
    }
}

struct DetailView3: View {
    @Environment(SheetStackModel.self) var sheetStackModel
    
    var body: some View {
        VStack {
            Text("Even more detail View")
            Button("Go back to View 2") {
                sheetStackModel.popOutOfStack()
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.ultraThinMaterial)
    }
}

struct ContentView: View {
    @State private var sheetStackModel = SheetStackModel()
    
    var body: some View {
        ZStack {
            ForEach(sheetStackModel.viewsItemManagedByThisSheet) { viewType in
                switch viewType {
                case .view1:
                    View1()
                case .view2:
                    View2()
                case .view3:
                    DetailView3()
                }
                // 设置层级:栈顶视图zIndex最高
                .zIndex(sheetStackModel.viewsItemManagedByThisSheet.last == viewType ? 1 : 0)
                // 非栈顶视图完全透明,避免材质叠加
                .opacity(sheetStackModel.viewsItemManagedByThisSheet.last == viewType ? 1 : 0)
                // 单独配置过渡动画
                .transition(.asymmetric(
                    insertion: .move(edge: .bottom),
                    removal: .move(edge: .bottom)
                ))
                // 用id确保视图状态正确刷新
                .id(viewType)
            }
        }
        .environment(sheetStackModel)
    }
}

关键修改说明

  • ForEach渲染所有视图:不再仅显示栈顶视图,而是渲染栈内所有视图,为堆叠效果提供基础
  • zIndex控制层级:栈顶视图zIndex设为1,确保始终覆盖在底层视图之上
  • opacity隐藏非栈顶视图:非栈顶视图透明度设为0,既保留视图存在以触发过渡,又避免ultraThinMaterial背景叠加
  • 独立过渡动画:每个视图单独设置过渡,确保滑入滑出时只有当前视图动,底层视图保持静止
  • id修饰符:确保视图切换时SwiftUI能正确识别视图身份,触发过渡动画

内容的提问来源于stack exchange,提问作者User95797654974

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:33:21