我正在尝试实现 SwiftUI View 的两种状态之间的平滑过渡。以下代码效果很好,当偏移量发生变化时,SwiftUI 将其解释为同一 View (相同标识)的 2 个状态,并通过 View 的幻灯片进行转换。
import SwiftUI
struct AnimationView: View {
@State var number: Int = 0
var body: some View {
VStack {
// VIEW #1
Image(systemName: "0.circle.fill")
// VIEW #2
// Image(systemName: "\(self.number).circle.fill")
.id("my-view-identity")
.offset(x: self.number%2==0 ? 50 : -50, y: 0)
Button {
self.number += 1
} label: {
Text("Add 1")
}
}
.animation(.linear(duration: 1), value: self.number)
}
}
struct AnimationView_Previews: PreviewProvider {
static var previews: some View {
AnimationView()
}
}
问题是当我尝试使用 VIEW #2 时,SwiftUI 无法解释 2 个状态 Image(systemName: "0.circle.fill")
和 Image(systemName: “1.circle.fill”)
作为同一 View 的 2 个状态,而是具有不同身份的 2 个不同 View ,即使我手动设置了 id。这会导致第一个 View 淡出,第二个 View 淡入。
我还尝试使用 subview 创建某种 View 代理并将固定 id 设置为该 View ,但这没有做任何事情。
我看过 WWDC21 演讲 Demystify SwiftUI 并且认为我对 View 标识有很好的掌握,但是这个我无法理解,我的猜测是我们可以为单个 View 手动设置不同的 id,但是我们不能做相反的事情:为不同的 View 设置相同的 id。我对吗 ?有什么技巧可以实现这一点吗?
最佳答案
方法1:ZStack
和.opacity()
这个技巧有效。将所有 ImageView 放入 ZStack
中,并使用 .opacity()
仅使当前 ImageView 可见:
struct AnimationView: View {
@State var number: Int = 0
var body: some View {
VStack {
ZStack {
ForEach(0..<51) { num in
Image(systemName: "\(num).circle.fill")
.opacity(self.number == num ? 1 : 0)
}
}
.offset(x: self.number % 2 == 0 ? 50 : -50, y: 0)
Button {
self.number += 1
} label: {
Text("Add 1")
}
}
.animation(.linear(duration: 1), value: self.number)
}
}
这是有效的,因为所有 View 都在屏幕上(只是不可见),因此当您移动它们时,它们不会被视为新 View ,而是刚刚更改了不透明度的 View 。这保留了对象标识并允许移动动画正常工作。
方法2:.id()
和.matchedGeometryEffect()
为 View 分配一个.id(self.number)
来区分 View ,并使用.matchedGeometryEffect()
来统一动画的 View 。
struct AnimationView: View {
@State var number: Int = 0
@Namespace var animation
var body: some View {
VStack {
Image(systemName: "\(self.number).circle.fill")
.matchedGeometryEffect(id: "id1", in: animation)
.offset(x: self.number % 2 == 0 ? 50 : -50, y: 0)
.id(self.number)
Button {
self.number += 1
} label: {
Text("Add 1")
}
}
.animation(.linear(duration: 1), value: self.number)
}
}
View 修饰符的顺序在这里至关重要。 .id()
需要位于.offset()
之后才能正常工作。
在这两种方法中,我更喜欢方法 2,因为它不会创建 50 个额外的 View !
关于swift - 如何在 SwiftUI 中保持过渡动画相同的标识,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76288452/