SwiftUI 动画 - Tabview 的问题

SwiftUI Animation - Issue with Tabview

我在选项卡视图中遇到动画问题。我有一个带有 2 个视图的 tabview。 第一个视图有一个带有动画的形状。第二个视图是一个简单的文本。 当我启动应用程序时,View1 出现并且动画正确。当我滑动到 View2 并返回到 View1 时,动画不再按预期显示并且有点随机。任何人都可能知道问题可能是什么?谢谢。

内容视图

import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            View1()
            View2()
        }   //: TAB
        .tabViewStyle(PageTabViewStyle())
        .padding(.vertical, 20)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

视图 1

import SwiftUI

struct FollowEffect: GeometryEffect {
    var pct: CGFloat = 0
    let path: Path
    var rotate = true

    var animatableData: CGFloat {
        get { return pct }
        set { pct = newValue }
    }

    func effectValue(size: CGSize) -> ProjectionTransform {

        if !rotate {
            let pt = percentPoint(pct)

            return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y))
        } else {
            // Calculate rotation angle, by calculating an imaginary line between two points
            // in the path: the current position (1) and a point very close behind in the path (2).
            let pt1 = percentPoint(pct)
            let pt2 = percentPoint(pct - 0.01)

            let a = pt2.x - pt1.x
            let b = pt2.y - pt1.y

            let angle = a < 0 ? atan(Double(b / a)) : atan(Double(b / a)) - Double.pi

            let transform = CGAffineTransform(translationX: pt1.x, y: pt1.y).rotated(by: CGFloat(angle))

            return ProjectionTransform(transform)
        }
    }

    func percentPoint(_ percent: CGFloat) -> CGPoint {

        let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)

        let f = pct > 0.999 ? CGFloat(1-0.001) : pct
        let t = pct > 0.999 ? CGFloat(1) : pct + 0.001
        let tp = path.trimmedPath(from: f, to: t)

        return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY)
    }
}

struct Solar2Grid: Shape {
    func path(in rect: CGRect) -> Path {
        return Solar2Grid.createArcPath(in: rect)
    }
    
    static func createArcPath(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: rect.width, y: 0))
        path.addLine(to: CGPoint(x: rect.width, y: rect.height - 20))
        path.addArc(center: CGPoint(x: rect.width - 20, y: rect.height - 20), radius: CGFloat(20), startAngle: .degrees(0), endAngle: .degrees(90), clockwise: false)
        path.addLine(to: CGPoint(x: 0, y: rect.height))
        return path
    }
}

struct AnimRecView: View {

    @State var flag: Bool = false
    var body: some View {
        ZStack {
            Solar2Grid()
                .stroke(Color.purple, style: StrokeStyle( lineWidth: 2, dash: [3]))
            Circle()
                .foregroundColor(Color.red)
                .blur(radius: 3.0)
                .frame(width: 8, height: 8).offset(x: -40, y: -40)
                .modifier(FollowEffect(pct: self.flag ? 1 :0, path: Solar2Grid.createArcPath(in: CGRect(x: 0, y: 0, width: 80, height: 80)), rotate: false))
                .onAppear {
                    withAnimation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false)) {
                            self.flag.toggle()
                    }
                }
        }
    }
}

struct View1: View {
    @State var flag: Bool = false
    var body: some View {
        VStack() {
            Text("View1")
            Spacer()
            HStack() {
                AnimRecView()
              }
            .frame(width: 80, height: 80, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
            Spacer()
        }
        .frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
        .background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .top, endPoint: .bottom))
        .cornerRadius(20)
        .padding(.horizontal, 20)
    }
}

struct View1_Previews: PreviewProvider {
    static var previews: some View {
        View1()
    }
}

视图 2

import SwiftUI

struct View2: View {
    var body: some View {
        Text("View2")
    }
}

struct View2_Previews: PreviewProvider {
    static var previews: some View {
        View2()
    }
}

问题是.onAppear()只调用了一次,所以下一次显示视图时,动画不知道要做什么。解决方法是在 Circle() 本身上放置一个显式动画。然后,当视图返回到屏幕上时,它就会有适当的动画。像这样:

struct AnimRecView: View {

    @State var flag: Bool = false
    
    var body: some View {
        ZStack {
            Solar2Grid()
                .stroke(Color.purple, style: StrokeStyle( lineWidth: 2, dash: [3]))
            Circle()
                .foregroundColor(Color.red)
                .blur(radius: 3.0)
                .frame(width: 8, height: 8).offset(x: -40, y: -40)
                .modifier(FollowEffect(pct: self.flag ? 1 : 0, path: Solar2Grid.createArcPath(in: CGRect(x: 0, y: 0, width: 80, height: 80)), rotate: false))
                // Put the explicit animation here
                .animation(Animation.linear(duration: 1.5).repeatForever(autoreverses: false), value: flag)
                .onAppear {
                    self.flag = true
                }
        }
    }
}