如何在SwiftUI中保持过渡动画的同一性

wf82jlnq  于 2023-05-21  发布在  Swift
关注(0)|答案(1)|浏览(167)

我试图在SwiftUI视图的两个状态之间实现平滑过渡。下面的代码工作得很好,当偏移量改变时,SwiftUI将其解释为相同视图的两个状态(相同的标识),并通过视图的幻灯片进行转换。

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")解释为同一视图的2个状态,而是2个具有不同标识的不同视图,即使我手动设置了ID。这会导致第一个视图的淡出和第二个视图的淡入。
我还尝试使用子视图来创建某种视图代理,并将固定id设置为该视图,但这没有任何作用。
我看过WWDC21的讨论Demystify SwiftUI,我认为我对视图标识有很好的把握,但这一个我无法理解,我的猜测是,我们可以手动为单个视图设置不同的id,但我们不能做相反的事情:为不同的视图设置相同的id.我说的对吗有没有什么窍门可以让它成为可能?

li9yvcax

li9yvcax1#

方法一:ZStack.opacity()

这个“诡计”奏效了。将所有图像视图放在ZStack中,并使用.opacity()仅使当前视图可见:

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)
    }
}

这样做是因为所有的视图都在屏幕上(只是不可见),所以当你移动它们时,它们不会被视为一个新的视图,而只是改变了不透明度。这将保留对象标识并允许移动动画正确工作。

方法二:.id().matchedGeometryEffect()

.id(self.number)指定给视图以区分视图,并使用.matchedGeometryEffect()统一动画的视图。

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)
    }
}

在这里,视图修改器的顺序非常重要。.id()需要在.offset()之后才能正常工作。
在这两种方法中,我更喜欢 * 方法2*,因为它不会创建50个额外的视图!

相关问题