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,问题出在两个地方:
- 转场方向理解错误:
.move(edge: .leading)表示视图从屏幕左侧外部移入当前位置,而非从右侧滑向左侧。你需要的右侧滑入效果,应该使用.move(edge: .trailing)作为插入动画。 - 转场层级重复应用:同时给父容器
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
相关产品推荐
相关产品推荐

