keegan3d
keegan3d

Reputation: 11305

SwiftUI Animation Shape with Array

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.

enter image description here

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

Answers (2)

Hajime
Hajime

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

hkatz
hkatz

Reputation: 961

The items you specify in animatableData themselves have to be animatable, ie conform to the Animatable protocol. This includes CGPoints, CGRects, CGSizes, and a few other types. Either that or they have to conform to the protocol VectorArithmetic, and this also includes Floats, CGFloats, and Doubles. If you do a search on GitHub for "animatableData", you should be able to pull up all sorts of examples.

Upvotes: 3

Related Questions