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

iOS 16中SwiftUI视图转场动画异常,是否为系统Bug?

SwiftUI 自定义转场动画问题排查与修复

问题描述

编写了如下SwiftUI代码,期望实现类似UITableView进入详情页的转场效果:

import SwiftUI

struct ContentView: View {
  @State var redVisible = false
  
  var body: some View {
    ZStack {
      Color.blue

      if redVisible {
        RedView($redVisible)
      }
    }
    .customTransitions()
    .onAppear{
      DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
        withAnimation {
          redVisible = true
        }
      }
    }
    
    .padding()
  }
}

struct RedView: View {
  @Binding var isVisible: Bool

  init(_ isVisible:Binding<Bool>) {
    self._isVisible = isVisible
  }
    var body: some View {
      ZStack {
        Color.red
      }
      .customTransitions()
      .onAppear {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
          withAnimation {
            isVisible = false
          }
        }
      }
    }
}

struct CustomTransitions: ViewModifier {
  
  func body(content: Content) -> some View {
    content
      .transition(returnTransition())
    
  }
  
  func returnTransition() -> AnyTransition {
    return .asymmetric(insertion: .move(edge:.leading),
                       removal: .move(edge:.trailing))
  }
  
}

extension View {
  func customTransitions() -> some View {
    self.modifier(CustomTransitions())
  }
}

实际效果如下:
动画实际效果

预期效果:

  • 初始显示蓝色视图
  • 2秒后红色视图从右侧滑入左侧
  • 再2秒后红色视图从左侧滑出至右侧

但实际出现两个问题:

  • 滑入方向与预期相反
  • 红色视图的移除动画完全不显示

问题原因

这不是iOS 16的Bug,问题出在两个地方:

  1. 转场方向理解错误:.move(edge: .leading) 表示视图从屏幕左侧外部移入当前位置,而非从右侧滑向左侧。你需要的右侧滑入效果,应该使用.move(edge: .trailing)作为插入动画。
  2. 转场层级重复应用:同时给父容器ZStack和子视图RedView添加了customTransitions(),导致RedView的移除动画被父容器的转场逻辑覆盖。当redVisible变为false时,RedView是被父视图移除,此时应该由父容器的转场负责处理移除动画,子视图无需单独添加转场。

修正方案

步骤1:移除子视图的转场修饰符

删除RedView中的.customTransitions()调用,避免转场逻辑冲突。

步骤2:调整转场方向

修改CustomTransitions中的转场方向,匹配你的预期效果。如果要实现“右侧滑入、左侧滑出”的导航式转场,代码如下:

import SwiftUI

struct ContentView: View {
  @State var redVisible = false
  
  var body: some View {
    ZStack {
      Color.blue

      if redVisible {
        RedView($redVisible)
      }
    }
    .customTransitions()
    .onAppear{
      DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        withAnimation {
          redVisible = true
        }
      }
    }
    .padding()
  }
}

struct RedView: View {
  @Binding var isVisible: Bool

  init(_ isVisible: Binding<Bool>) {
    self._isVisible = isVisible
  }
  
  var body: some View {
    Color.red
      .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
          withAnimation {
            isVisible = false
          }
        }
      }
  }
}

struct CustomTransitions: ViewModifier {
  func body(content: Content) -> some View {
    content
      .transition(returnTransition())
  }
  
  func returnTransition() -> AnyTransition {
    // 插入:从右侧滑入;移除:向左侧滑出
    return .asymmetric(
      insertion: .move(edge: .trailing),
      removal: .move(edge: .leading)
    )
  }
}

extension View {
  func customTransitions() -> some View {
    self.modifier(CustomTransitions())
  }
}

效果说明

修正后:

  • 初始显示蓝色视图
  • 2秒后红色视图从屏幕右侧滑入,覆盖蓝色视图
  • 再2秒后红色视图从屏幕左侧滑出,露出蓝色视图,完全匹配你想要的导航转场效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 07:06:30