2

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
                                    )
                                }
                            }
                    )
            }
        }
    }
}

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.