I’m animating a line horizontally and it works fine, except that the effect I’m aiming for is to have a slight springy bounce before it alternates sides. I thought the .easeInOut
effect would emulate that but it doesn’t feel springy. How can I combine the .spring
animation such that the line sort of bounces for a fraction of a second at each end before it moves to the alternate side?
struct Line: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: rect.size.height / 2))
path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height / 2))
return path
}
}
struct LineView: View {
let height: CGFloat
@State private var animating = false
var body: some View {
GeometryReader { geometry in
let lineWidth = geometry.size.width / 3
Line()
.stroke(Color.black, lineWidth: height)
.frame(width: lineWidth)
.offset(x: animating ? 0 : geometry.size.width - lineWidth)
.animation(.easeInOut(duration: 1.0).repeatForever(), value: animating)
.onAppear {
animating.toggle()
}
}
}
}
struct ContentView: View {
var body: some View {
VStack {
LineView(height: 5.0)
.frame(width: 200)
}
}
}
Any help is appreciated.
The intended effect (blue line): https://streamable.com/ijwe1w
2
Answers
You are on the right track, but you are over engineering your solution. Essentially, you need to make 3 layers. The first is a background as perceived by the user, but it is really the base view. On top of that, you add your visual. For simplicity, I used capsules. The top layer is a mask which is where the magic happens.
To achieve the look you desire, the middle layer should overshoot the bottom layer in the offset, by how much you want it to "shrink". You then put a mask on top that is the same size as the bottom layer to act as a window that only allows the user to see that much of what is happening below. Here is what I believe you are looking for:
In the end, you get this:
(Note, this is a gif, and not as smooth as the actual animation)
It works quite well if you just animate the leading and trailing padding and use negative padding at the sides. The shape then needs to be clipped, so that the effect of the negative padding is to make the line look shorter.
It is not really necessary to use a
Line
shape. You can just use aRectangle
as both the filled shape and the clip shape. Alternatively, if you would like rounded corners, use aCapsule
.It can be done by setting the x-offset instead of padding. This works too (and is closer to what you originally had):