SwiftUI 状态变量动画触发视图重载时出现短暂淡入淡出闪烁问题
问题根因
- 原有实现使用静态数组存储柱形数据,不属于视图状态,无法被SwiftUI自动监听变更,你额外添加的无意义
if/else分支会强制整组柱形视图销毁重建,是导致闪烁的核心原因 ForEach遍历索引而非元素本身,SwiftUI会默认索引对应位置的视图不变,仅更新高度属性,无法触发位置动画
修复后的完整代码
import SwiftUI class SortElement: Identifiable{ var id: Int var name: String var color: Color var height: CGFloat init(id: Int, name: String, color: Color, height: CGFloat){ self.id = id self.name = name self.color = color self.height = height } } struct SortArrayAnimationDemo: View { @Namespace private var animationSwap // 将数组改为视图状态变量,支持自动监听变更 @State private var sortableElements: [SortElement] = [ SortElement(id: 6, name:"6", color: Color(UIColor.lightGray), height: 60), SortElement(id: 2, name:"2", color: Color(UIColor.lightGray), height: 20), SortElement(id: 5, name:"5", color: Color(UIColor.lightGray), height: 50), SortElement(id: 1, name:"1", color: Color(UIColor.lightGray), height: 10), SortElement(id: 3, name:"3", color: Color(UIColor.lightGray), height: 30), SortElement(id: 9, name:"9", color: Color(UIColor.lightGray), height: 90), SortElement(id: 4, name:"4", color: Color(UIColor.lightGray), height: 40), SortElement(id: 8, name:"8", color: Color(UIColor.lightGray), height: 80) ] var body: some View { VStack{ RoundedRectangle(cornerRadius: 10.0) .fill(Color.blue) .frame(width: 200.0, height: 20.0) // 去掉无意义的if/else分支,直接调用视图构建方法 fetchSwappingView() RoundedRectangle(cornerRadius: 10.0) .fill(Color.blue) .frame(width: 200.0, height: 20.0) Button(action: { doAnimate() }) { Text("Swap!") }.padding(.top, 30) } } @ViewBuilder private func fetchSwappingView() -> some View { HStack(alignment: .bottom){ RoundedRectangle(cornerRadius: 10.0) .fill(Color.red) .frame(width: 30.0, height: 100.0) // 直接遍历元素而非索引,匹配元素id做动画 ForEach(sortableElements) { element in RoundedRectangle(cornerRadius: 10.0) .fill(Color.gray) .frame(width: 20.0, height: element.height) .matchedGeometryEffect(id: element.id, in: animationSwap, properties: .position) } RoundedRectangle(cornerRadius: 10.0) .fill(Color.red) .frame(width: 30.0, height: 100.0) } } private func doAnimate() -> Void { let randomIndex = Int.random(in: 1..<8) // 交换数组元素,状态变更自动触发视图更新 sortableElements.swapAt(0, randomIndex) // 直接包裹状态变更触发动画,不需要额外toggle状态 withAnimation(.spring(dampingFraction: 0.7).speed(1.5).delay(0.05)){} } }
修改说明
- 将静态数组改为
@State修饰的视图私有状态,SwiftUI会自动监听数组的元素变更,差分更新对应视图,无需强制整组重建 ForEach直接遍历遵守Identifiable协议的SortElement实例,系统会根据元素id关联对应视图,配合matchedGeometryEffect自动识别元素的位置变化- 移除了无实际作用的
animate状态和if/else分支,彻底解决视图强制销毁重建导致的闪烁问题 - 交换数组元素后直接通过
withAnimation包裹触发动画,逻辑更简洁,动画流畅无闪烁
内容的提问来源于stack exchange,提问作者Rich Glowzenski
相关产品推荐
相关产品推荐

