首页 > 解决方案 > SwiftUI 动画形状变得疯狂

问题描述

首先,我对这篇文章的非传统标题表示歉意,但我不知道有什么方法可以比这更好地描述这种行为。

要重现此问题,请在 xCode 12 中创建一个新项目,指定任何名称的 IOS App 和 **Interface:**SwiftUI, Life Cycle: SwiftUI App, Language: Swift。然后将所有内容视图替换为此处列出的代码。

在实时预览中,或在运行应用程序时,单击多边形将触发动画。它应该移动一小部分像素,然后转 1/3 圈(因为角度以弧度为单位)。如第 37 行所述,问题在于,当我尝试移动或转动 Poly 时,它会发疯,将任何移动乘以更大的数量。颜色的动画效果很好,但形状中的 Animatable 属性却不行。该位置从 200,200 开始,角度为 0,如果您尝试将其移得很近,就像示例代码一样,它会反应过度。如果您尝试仅将其移动几个像素(例如 190,190),它将飞出屏幕。

我所做的任何其他动画都没有发生这种情况,也不知道为什么会这样。

在尝试对此进行调试时,我曾在 animatableData getter 和 setter 上放置了 print 语句,并且无法理解动画引擎对变量所做的事情。它似乎只是选择了一个离我要求它去的值的来源更远的数字。

我相信路径中的触发是正确的,并且怀疑问题出在以下之一:

我正在运行 Xcode 12.1 (12A7403) 和 Swift 5。经过数小时的尝试解决这个问题后,我在这里谦虚地提出我的问题。 帮助我 Obiwan Kenobi,你是我唯一的希望......

在此处输入图像描述

import SwiftUI

let twoPi:CGFloat = CGFloat.pi * 2
let pi = CGFloat.pi

class Poly: ObservableObject, Identifiable {

    @Published var location:CGPoint
    @Published var color:Color

    var sides:CGFloat
    var vertexRadius:CGFloat
    var angle:CGFloat

    init(at:CGPoint, color:Color, sides:CGFloat, radius:CGFloat, angle:CGFloat=0) {
        self.location = at
        self.color = color
        self.sides = sides
        self.vertexRadius = radius
        self.angle = angle
    }
}

struct ContentView: View {

    @ObservedObject var poly:Poly = Poly(at: CGPoint(x:200,y:200), color: .green, sides: 6, radius: 100)
    
    var body: some View {

        PolyShape(poly: poly)
            .fill(poly.color)
            .gesture(
                DragGesture(minimumDistance: 0, coordinateSpace: .local)
                    .onEnded { gesture in
                        withAnimation(.easeInOut(duration:15)) {
                            //This is what doesn't work.
                            //Try to nudge it a fraction of a pixel, and do only 1/3 of a turn, and it spins and moves much further.
                            poly.location.x = 200.4
                            poly.location.y = 200.2
                            poly.angle = twoPi / 3
                            poly.color = .red
                        }
                    }
            )
    }
}

struct ContentView_Previews: PreviewProvider {

    static var previews: some View {
        Group {
            ContentView(poly: Poly(at: CGPoint(x:200,y:200), color: .blue, sides: 3, radius: 100))
        }
    }
}

struct PolyShape:Shape {

    var poly:Poly

    public var animatableData: AnimatablePair<CGFloat, AnimatablePair<CGFloat,CGFloat>>  {
        get { AnimatablePair(poly.angle, AnimatablePair(poly.location.x, poly.location.y))
        }
        set {
            poly.angle = newValue.first
            poly.location.x = newValue.second.first
            poly.location.y = newValue.second.second
        }
    }

    func path(in rect: CGRect) -> Path {

        var path = Path()
        var radial:CGFloat = 0.5

        while radial < twoPi + 0.5 {
            let radialAngle = twoPi / poly.sides * radial + poly.angle
            let newX = poly.location.x + cos(radialAngle) * poly.vertexRadius
            let newY = poly.location.y + sin(radialAngle) * poly.vertexRadius
            if radial == 0.5 {
                path.move(to: CGPoint(x: newX, y: newY))
            } else {
                path.addLine(to: CGPoint(x: newX, y: newY))
            }
            radial += 1
        }
        return path
    }
}

标签: iosanimationswiftui

解决方案


您需要将模型视图模型分开,因为要PolyShape在您的情况下正确工作,输入数据必须有一个值。

这是经过测试的解决方案(Xcode 12 / iOS 14)

演示

  1. 分离模型和视图模型
class Poly: ObservableObject, Identifiable {
    @Published var data:PolyData

    init(data: PolyData) {
        self.data = data
    }
}

struct PolyData {
    var location:CGPoint
    var color:Color

    var sides:CGFloat
    var vertexRadius:CGFloat
    var angle:CGFloat

    init(at:CGPoint, color:Color, sides:CGFloat, radius:CGFloat, angle:CGFloat=0) {
        self.location = at
        self.color = color
        self.sides = sides
        self.vertexRadius = radius
        self.angle = angle
    }
}
  1. 使形状值依赖
struct PolyShape:Shape {

    var poly:PolyData      // << here !!

    // ... other code no changes
}
  1. 更新依赖演示
struct ContentView: View {

    @ObservedObject var poly:Poly = Poly(data: PolyData(at: CGPoint(x:200,y:200), color: .green, sides: 6, radius: 100))
    
    var body: some View {

        PolyShape(poly: poly.data)     // << pass data only here !!
            .fill(poly.data.color)
            .gesture(
                DragGesture(minimumDistance: 0, coordinateSpace: .local)
                    .onEnded { gesture in
                        withAnimation(.easeInOut(duration:15)) {
                            poly.data.location = CGPoint(x: 200.4, y: 200.2)
                            poly.data.angle = twoPi / 3
                            poly.data.color = .red
                        }
                    }
            )
    }
}

struct ContentView_Previews: PreviewProvider {

    static var previews: some View {
        Group {
            ContentView(poly: Poly(data: PolyData(at: CGPoint(x:200,y:200), color: .blue, sides: 3, radius: 100)))
        }
    }
}

推荐阅读