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.
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 }
            }
        
    }
}
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 tutorials about the Animatable protocol I can find online all use examples of 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".
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 }
            }
    }
}
Thanks for your kind reading, and maybe oncoming answers!
 
     
    