If SwiftUI doesn't provide an option to speed up the reverse animation, then a way to achieve it is by applying your own Animatable
view modifier. You need to disable the built-in reverse animation by applying .repeatForever(autoreverses: false)
. Then implement the reverse part of the animation as the tail end of the "forward" animation.
Here's an example:
struct OffsetModifier: ViewModifier, Animatable {
private let maxOffset: CGFloat
private let rewindSpeedFactor: Int
private var progress: CGFloat
// The progress value at which rewind begins
private let rewindThreshold: CGFloat
init(
maxOffset: CGFloat,
rewindSpeedFactor: Int = 4,
progress: CGFloat
) {
self.maxOffset = maxOffset
self.rewindSpeedFactor = rewindSpeedFactor
self.progress = progress
// Compute the threshold at which rewind begins
self.rewindThreshold = CGFloat(1) - (CGFloat(1) / CGFloat(rewindSpeedFactor + 1))
}
/// Implementation of protocol property
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
var xOffset: CGFloat {
let fraction: CGFloat
if progress > rewindThreshold {
fraction = rewindThreshold - ((progress - rewindThreshold) * CGFloat(rewindSpeedFactor))
} else {
fraction = progress
}
return (fraction / rewindThreshold) * maxOffset
}
func body(content: Content) -> some View {
content.offset(x: xOffset)
}
}
struct ContentView: View {
private let diameter = CGFloat(30)
@State private var progress = CGFloat.zero
var body: some View {
VStack {
GeometryReader { proxy in
Circle()
.fill()
.foregroundColor(.red)
.frame(width: diameter, height: diameter)
.modifier(OffsetModifier(maxOffset: proxy.size.width - diameter, progress: progress))
.animation(.linear(duration: 3.0).repeatForever(autoreverses: false), value: progress)
}
.frame(height: diameter)
.onAppear { progress = 1.0 }
}
}
}
