SwiftUI的onAppear中直接调用withAnimation为什么会出现界面异常?
struct MyShimmer: View { @State var show = false var body: some View { ZStack { Color.black VStack { ZStack { Text("Chao") .foregroundColor(Color.white.opacity(0.5)) .font(.system(size: 40)) Text("Chao") .foregroundColor(Color.white) .font(.system(size: 40)) .mask( Color.red.opacity(0.5) .frame(width: 20) .offset(x: self.show ? 180 : -130) ) } .onAppear(){ DispatchQueue.main.async { withAnimation(Animation.default.speed(0.15).delay(0).repeatForever(autoreverses: false)){ self.show.toggle() } } } } } } }
问题原因说明
这个现象是SwiftUI旧版本中onAppear的调用时机和动画事务的作用范围共同导致的,具体逻辑如下:
onAppear的触发时机是视图刚被加入渲染树、还未完成初始布局计算的阶段。这个时候直接在onAppear闭包内执行withAnimation修改状态,当前所有未完成的布局变更(包括整个视图从默认的zero frame到最终屏幕位置的布局过程)都会被纳入当前动画事务,所以你会看到整个视图从左上角缩放弹出的效果,动画意外作用到了整个视图的布局过程,而不是仅作用在你想要的mask偏移上。- 加
DispatchQueue.main.async之后,修改状态的逻辑会被推迟到主队列的下一个RunLoop执行,此时视图已经完成了初始布局,确定了自己在屏幕上的最终位置和尺寸,后续触发的动画只会响应show状态对应的mask偏移变化,自然就不会出现异常的整体弹出效果。 - 放在按钮点击事件中不会出问题的原因同理:按钮能够响应点击事件时,视图已经完成了初始布局并稳定渲染在屏幕上,此时触发的动画只会作用于你修改的状态对应的属性变更,不会牵连初始布局过程。
补充说明:这个问题属于SwiftUI 2.0(对应Xcode 12版本)的已知常见问题,后续新版本SwiftUI调整了onAppear的调用时机,已经大幅降低了这类问题的出现概率。如果要兼容旧版本,除了用DispatchQueue.main.async之外,也可以给不需要参与动画的外层视图添加.animation(nil, value: show)修饰符,隔离状态变更对整体布局的动画影响。
内容的提问来源于stack exchange,提问作者Chao Huang
相关产品推荐
相关产品推荐

