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

SwiftUI视图切换动态垂直滑动过渡效果实现求助

可以通过SwiftUI Transition实现该效果

你的需求完全可以通过SwiftUI的AnyTransition.asymmetric实现,它允许我们分别控制视图插入和移除时的过渡动画方向,结合保存的上一个视图索引就能精准匹配你要的滑动逻辑。

核心修改点:

  • 使用AnyTransition.asymmetric区分新视图插入、旧视图移除的动画方向
  • 通过ZStack同时渲染新旧视图,保证移除动画可见
  • 用withAnimation包裹状态变化,确保动画触发生效

修改后的完整代码:

enum ViewScreen: String, CaseIterable, Identifiable {
    case target1
    case target2
    case target3
    case target4
    case target5
        
    var id: String { return self.rawValue }
    var index: Int { ViewScreen.allCases.firstIndex(of: self) ?? 0 }
}

// 示例视图(替换为你自己的View1-View5即可)
struct View1: View { var body: some View { Text("Target 1").frame(maxWidth: .infinity, maxHeight: .infinity).background(.blue) } }
struct View2: View { var body: some View { Text("Target 2").frame(maxWidth: .infinity, maxHeight: .infinity).background(.green) } }
struct View3: View { var body: some View { Text("Target 3").frame(maxWidth: .infinity, maxHeight: .infinity).background(.orange) } }
struct View4: View { var body: some View { Text("Target 4").frame(maxWidth: .infinity, maxHeight: .infinity).background(.purple) } }
struct View5: View { var body: some View { Text("Target 5").frame(maxWidth: .infinity, maxHeight: .infinity).background(.pink) } }

struct SidebarView: View {
    @Binding var target: ViewScreen
    var body: some View {
        VStack(spacing: 16) {
            ForEach(ViewScreen.allCases) { screen in
                Button(screen.rawValue) {
                    withAnimation(.easeInOut(duration: 0.5)) {
                        target = screen
                    }
                }
                .padding()
                .background(target == screen ? .gray.opacity(0.3) : .clear)
                .cornerRadius(8)
            }
        }
        .padding()
        .frame(width: 150)
        .background(.gray.opacity(0.1))
    }
}

struct ContentView: View {
    @State var target: ViewScreen = .target1
    @State var previousTarget: ViewScreen = .target1
    
    private var customTransition: AnyTransition {
        let isMovingUp = previousTarget.index > target.index // 判断是否为向上切换(如target3→target1)
        return AnyTransition.asymmetric(
            insertion: isMovingUp ? .move(edge: .bottom) : .move(edge: .top),
            removal: isMovingUp ? .move(edge: .bottom) : .move(edge: .top)
        )
        .combined(with: .opacity)
    }
    
    var body: some View {
        HStack(alignment: .top, spacing: 0) {
            SidebarView(target: $target)
            ZStack {
                // 渲染上一个视图,用于展示移除动画
                switch previousTarget {
                case .target1: View1()
                case .target2: View2()
                case .target3: View3()
                case .target4: View4()
                case .target5: View5()
                }
                // 渲染当前视图,用于展示插入动画
                switch target {
                case .target1: View1()
                case .target2: View2()
                case .target3: View3()
                case .target4: View4()
                case .target5: View5()
                }
            }
            .transition(customTransition)
        }
        .onChange(of: target) { _ in
            previousTarget = target
        }
    }
}

关键逻辑说明:

  1. 不对称过渡配置:
    • 向上切换(如target3→target1):新视图从底部滑入,旧视图向底部滑出
    • 向下切换(如target1→target3):新视图从顶部滑入,旧视图向顶部滑出
  2. ZStack的作用:同时保留新旧视图,确保移除动画不会因为视图被直接销毁而无法展示
  3. 动画触发:侧边栏按钮点击时用withAnimation包裹状态更新,强制触发过渡动画

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:10:13