So... AnimatableModifier is now deprecated, but how to "use Animatable directly"?

I am aiming to achieve a callback when an animation of offset change is finished. So, I found a workaround online which uses the AnimatableModifier to check when the animatableData equals the target value.

But it turns out AnimatableModifier is now deprecated. And I cannot find an alternative to it.

I am aware that GeometryEffect will work for this case of offset change, where you could use ProjectionTransform to do the trick. But I am more concerned about the official recommendation to "use Animatable directly".

Seriously, the only tutorial about the Animatable protocol I can find online is examples of using the Shape struct which implicitly implements the Animatable protocol.

And the following code I improvised with the Animatable protocol doesn't even do the "animating".

Thanks for your kind reading, and maybe oncoming answers!

Accepted Answer

That would really help to post the code also as text.

Replacing by

    var body: some View {
        RectView(value: offsetY)
            .onAppear {
                withAnimation(.easeInOut) { offsetY = 100 }
            }
    }

gives an animation effect.

You can also adjust speed:

                withAnimation(.easeInOut.speed(0.1)) { offsetY = 100 }

I also removed .animation(nil)

    var body: some View {
        Rectangle()
            .frame(width: 100, height: 100, alignment: .center)
            .offset(y: value)
    }

I can't seem to update my post now. But the two code snippets are pasted below.

struct OffsetAnimation: AnimatableModifier{
    typealias T = CGFloat
    var animatableData: T{
        get { value }
        set {
            value = newValue
            print("animating \(value)")
            if watchForCompletion && value == targetValue {
                DispatchQueue.main.async { [self] in onCompletion() }
            }
        }
    }
    var watchForCompletion: Bool
    var value: T
    var targetValue: T
    init(value: T, watchForCompletion: Bool, onCompletion: @escaping()->()){
        self.targetValue = value
        self.value = value
        self.watchForCompletion = watchForCompletion
        self.onCompletion = onCompletion
    }
    var onCompletion: () -> ()
    func body(content: Content) -> some View {
        return content.offset(x: 0, y: value).animation(nil)
    }
}

struct DemoView: View {
    @State var offsetY: CGFloat = .zero
    var body: some View {
        Rectangle().frame(width: 100, height: 100, alignment: .center)
            .modifier(
                OffsetAnimation(value: offsetY,
                                watchForCompletion: true,
                                onCompletion: {print("translation complete")}))
            .onAppear{
                withAnimation{ offsetY = 100 }
            }
        
    }
}
struct RectView: View, Animatable{
    typealias T = CGFloat
    var animatableData: T{
        get { value }
        set {
            value = newValue
            print("animating \(value)")
        }
    }
    var value: T
    var body: some View{
        Rectangle().frame(width: 100, height: 100, alignment: .center)
            .offset(y:value).animation(nil)
    }
}

struct DemoView: View{
    @State var offsetY: CGFloat = .zero
    var body: some View {
        RectView(value: offsetY)
            .onAppear{
                withAnimation{ offsetY = 100 }
            }
    }
}

Conform your view modifier to ViewModifier and Animatable explicitly.

So... AnimatableModifier is now deprecated, but how to "use Animatable directly"?
 
 
Q