SwiftUI中如何在ForEach外维持视图标识以实现动画效果?
在SwiftUI中不使用ForEach维持视图标识实现交换动画
要实现和ForEach案例相同的色块交换动画,核心是给每个视图分配固定的唯一显式标识,让SwiftUI能跟踪视图的身份,即使它们的位置或属性变化,也能识别出是同一个视图而非全新实例。
正确的实现方式
struct SimplePreview: PreviewProvider, View { static var previews = Self() @State private var isRunning = false var body: some View { VStack { Button("Toggle is \(isRunning ? "on" : "off")") { withAnimation { isRunning.toggle() } } // 目标实现:显式声明视图,维持标识实现交换动画 HStack { if isRunning { // 固定id标识蓝色块 Rectangle() .fill(Color.blue) .id("blue_block") // 固定id标识灰色块 Rectangle() .fill(Color.gray) .id("gray_block") } else { // 交换位置,但保留各自的固定id Rectangle() .fill(Color.gray) .id("gray_block") Rectangle() .fill(Color.blue) .id("blue_block") } } } } }
原理说明
- 案例2中,两个矩形是结构上固定的独立视图,SwiftUI根据它们在HStack中的位置生成结构标识。状态变化时,只是修改各自的填充色,不会触发位置移动动画。
- 案例3错误地使用了随状态变化的
id,导致每次切换时,视图的显式标识改变,SwiftUI会将其视为全新视图,销毁旧实例并创建新实例,自然丢失动画。 - 给每个视图分配固定的显式id后,SwiftUI会优先使用这个id作为视图标识。当isRunning切换时,虽然视图在HStack中的位置交换,但它们的id不变,SwiftUI能识别出是同一个视图的位置变化,从而触发平滑的移动动画,效果和ForEach案例一致。
另一种方式:使用matchedGeometryEffect
如果需要更灵活的位置过渡,也可以用@Namespace配合matchedGeometryEffect,通过共享命名空间关联视图的几何信息:
struct SimplePreview: PreviewProvider, View { static var previews = Self() @State private var isRunning = false @Namespace private var animationNamespace var body: some View { VStack { Button("Toggle is \(isRunning ? "on" : "off")") { withAnimation { isRunning.toggle() } } HStack { if isRunning { Rectangle() .fill(Color.blue) .matchedGeometryEffect(id: "blue_block", in: animationNamespace) Rectangle() .fill(Color.gray) .matchedGeometryEffect(id: "gray_block", in: animationNamespace) } else { Rectangle() .fill(Color.gray) .matchedGeometryEffect(id: "gray_block", in: animationNamespace) Rectangle() .fill(Color.blue) .matchedGeometryEffect(id: "blue_block", in: animationNamespace) } } } } }
这种方式会让视图的位置和大小过渡更连贯,适合复杂布局场景。
内容的提问来源于stack exchange,提问作者lhunath
相关产品推荐
相关产品推荐

