Reputation: 11305
I'm trying to animate a custom shape in SwiftUI derived from data in an array of objects. I've created this sample to illustrate the problem I'm encountering trying to get this working.
In this simple example. After a red dot is dragged it offsets its X and Y with a spring animation. However the shape snaps to the new position instead of smoothly animating with the dot.
I've tried several versions on animatableData
but still haven't been able to get the animation working. Hope someone with more knowledge of this can help! Thank you.
final class Item: Identifiable {
let id: String
var position: CGPoint
let radius: CGFloat = 10
var offset: CGSize {
CGSize(width: position.x-radius, height: position.y-radius)
}
init(id: String, position: CGPoint) {
self.id = id
self.position = position
}
}
final class Manager: ObservableObject {
var items = [
Item(id: "Item 1", position: .init(x: 50, y: 200)),
Item(id: "Item 2", position: .init(x: 200, y: 200)),
Item(id: "Item 3", position: .init(x: 200, y: 50))
]
}
struct CustomShape: Shape {
@ObservedObject var manager: Manager
var animatableData: [Item] {
get { manager.items }
set { manager.items = newValue }
}
func path(in rect: CGRect) -> Path {
Path { path in
for i in 0..<animatableData.count {
let item = animatableData[i]
if i == 0 {
path.move(to: item.position)
} else {
path.addLine(to: item.position)
}
}
}
}
}
struct ContentView: View {
@ObservedObject var manager = Manager()
var body: some View {
ZStack(alignment: .topLeading) {
CustomShape(manager: manager)
ForEach(manager.items) { item in
Circle().foregroundColor(.red)
.frame(width: item.radius*2, height: item.radius*2)
.offset(item.offset)
.gesture(
DragGesture()
.onChanged { gesture in
manager.objectWillChange.send()
item.position = gesture.location
}
.onEnded { gesture in
withAnimation(.spring()) {
manager.objectWillChange.send()
item.position = CGPoint(
x: item.position.x + 20,
y: item.position.y + 20
)
}
}
)
}
}
}
}
Upvotes: 2
Views: 798
Reputation: 373
I managed to resolve the issue of animating a custom shape based on an array of data in SwiftUI by creating a custom wrapper that conforms to the VectorArithmetic protocol.
This allows SwiftUI's animation framework to understand how to interpolate between the data points in my array, providing a smooth animation.
Specifically, I wrapped my [Float] array using this custom VectorArithmetic wrapper, making it possible to use it as animatableData in my custom Shape.
For anyone facing similar issues, I highly recommend checking out this article by Majid.
This approach worked well for me and should be applicable to anyone trying to animate custom shapes based on arrays of data in SwiftUI.
Upvotes: 2
Reputation: 961
The items you specify in animatableData
themselves have to be animatable, ie conform to the Animatable
protocol. This includes CGPoint
s, CGRect
s, CGSize
s, and a few other types. Either that or they have to conform to the protocol VectorArithmetic
, and this also includes Float
s, CGFloat
s, and Double
s. If you do a search on GitHub for "animatableData", you should be able to pull up all sorts of examples.
Upvotes: 3