SwiftUI实现类iOS默认导航的ZStack视图过渡动画方案
解决SwiftUI实现类似iOS导航/地图Sheet的堆叠滑入滑出效果
问题根源
原代码仅在ZStack中渲染当前栈顶视图,无法形成视图堆叠结构,导致过渡动画只能触发简单的透明度变化,同时无法实现上层视图覆盖底层的效果。此外,非栈顶视图未被正确隐藏,会导致ultraThinMaterial背景叠加影响视觉效果。
解决方案
- 在ZStack中渲染栈内所有视图,通过
zIndex控制层级,确保栈顶视图始终在最上层 - 对非栈顶视图设置完全透明(
opacity(0)),避免背景材质叠加 - 为每个视图单独配置不对称过渡动画,实现从底部滑入/滑出的效果
- 利用
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
相关产品推荐
相关产品推荐

