First off, my appologies for the unconventional title of this post, but I don't know of any way to describe the behavior any better than that.
To reproduce this problem, create a new project in xCode 12 specifying IOS App with any name and **Interface:**SwiftUI, Life Cycle: SwiftUI App, Language: Swift. Then replace all of Content View with the code listed here.
In either Live Preview, or when running the app, a click on the Poly will trigger the animation. It should move a fraction of a pixel, and do a 1/3 turn (as the angle is in radians). The problem, as noted on line 37 is that when I try to move or turn the Poly, it goes crazy, multiplying any move by much greater amounts. The color animates fine, but the Animatable properties in the shape do not. The location starts at 200,200 with an angle of 0, and if you try to move it very close, as the sample code does, it overreacts. If you try to move it only a few pixes (say 190,190) it will fly off the screen.
I have not had this happen to any other animations I have done, and have no idea why this is behaving this way.
In trying to debug this, at one point I put print statements on the animatableData getters and setters, and can make no sense of what the animation engine is doing to the variables. It just seems to pick a number that is much further from the source that the value I am asking it to go to.
I am confident that the trig in the path is correct, and suspect the issue lies in one of the following:
- My declaration of animatableData somehow
- The withAnimation function in the gesture
- An issue with animating a CGFloat
- SwiftUI is just going crazy
I am running Xcode 12.1 (12A7403) and Swift 5. After many hours of trying to figure this out, I humbly present my problem here. Help me Obiwan Kenobi, you are my only hope...
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
}
}

