问题
I don't know how to stop withAnimation(_:)
method after starting it.
I am writing my first app with SwiftUI and I want to create a progress circle, which will be controlled with buttons by user - start button, which will start animation, where circle will be unfilled at the end and stop button will have to save actual state of the fill point and stop the animation.
My main view:
struct MainView: View {
@State private var fillPoint = 1.0
@State private var animationDuration = 10.0
private var ring: Ring {
let ring = Ring(fillPoint: self.fillPoint)
return ring
}
var body: some View {
VStack {
ring.stroke(Color.red, lineWidth: 15.0)
.frame(width: 200, height: 200)
.padding(40)
HStack {
Button(action: {
withAnimation(.easeIn(duration: self.animationDuration)) {
self.fillPoint = 0
}
}) {
Text("Start")
}
Button(action: {
// what should I do here?
}) {
Text("Stop")
}
}
}
}
}
And Ring's struct:
struct Ring: Shape {
var startArcAngle: Double = 360.0
var fillPoint: Double {
willSet {
startArcAngle = 360 * newValue
}
}
internal var animatableData: Double {
get { return fillPoint }
set { fillPoint = newValue }
}
internal func path(in rect: CGRect) -> Path {
let endArcAngle = 0.0
var path = Path()
path.addArc(center: CGPoint(x: rect.size.width / 2,
y: rect.size.height / 2),
radius: rect.size.width / 2,
startAngle: .degrees(startArcAngle - 90),
endAngle: .degrees(endArcAngle - 90),
clockwise: true)
return path
}
}
I've tried to manipulate animatableData
value, but outside Ring's struct it's always returning 0.0 (value which is my code going to achieve at the end of animation if it's started) and inside Ring's struct it prints as I would like (0.96xxxx -> 0.94xxxx, etc.), but taking this outside of Ring's struct always returns 1.0 or 0.0.
回答1:
It looks like there is no control to stop animation.
As your requirement is to start and stop the draw in the middle of progress, one alternate solution is to use a Timer
. The tricky point is to clear the arc based on the timer duration.
Here is the code I made a change in your MainView
:
NOTE: Adjust the animation duration based on your choice.
struct MainView: View {
@State private var fillPoint = 1.0
@State private var animationDuration = 10.0
@State private var stopAnimation = true
@State private var countdownTimer: Timer?
private var ring: Ring {
let ring = Ring(fillPoint: self.fillPoint)
return ring
}
var body: some View {
VStack {
ring.stroke(Color.red, lineWidth: 15.0)
.frame(width: 200, height: 200)
.padding(40)
.animation(self.stopAnimation ? nil : .easeIn(duration: 0.1))
HStack {
Button(action: {
self.stopAnimation = false
self.countdownTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in
guard self.animationDuration > 0 else {
self.countdownTimer?.invalidate()
return
}
self.fillPoint = self.animationDuration/10
self.animationDuration -= 0.1
})
}) {
Text("Start")
}
Button(action: {
self.countdownTimer?.invalidate()
self.stopAnimation = true
}) {
Text("Stop")
}
}
}
}
}
回答2:
You can not interrupt an animation BUT you can make it seem like an interruption. You can use Group and when the view is not animating, you can use a static view. Here is an example for elaboration;
struct RingView: View {
@ObservedObject var viewModel: ViewModel
@State private var animatedBonusRemaining: Double = 1
private func startBonusTimeAnimation() {
animatedBonusRemaining = viewModel.remainingTimeRatio
withAnimation(.linear(duration: viewModel.remainingDuration)) {
animatedBonusRemaining = 0
}
}
var body: some View {
ZStack {
Pie()
.opacity(0.4)
Group {
if viewModel.isRunning {
Pie(startAngle: Angle(degrees: -90),
endAngle: Angle(degrees: (-360 * animatedBonusRemaining) - 90))
} else {
Pie(startAngle: Angle(degrees: -90),
endAngle: Angle(degrees: (-360 * viewModel.remainingTimeRatio) - 90))
}
}
}
.aspectRatio(1.0, contentMode: .fill)
.onTapGesture {
routine.toggleIsRunning()
if routine.isRunning {
startBonusTimeAnimation()
}
}
}
Remaining Ratio is a computed property which is easily computed by remaining time over total duration. I am using a timer in my view model but do not expose it to View since I think the view itself do not have job with a timer. It can animate via withAnimation closure. I use the timer only for detection of completion.
来源:https://stackoverflow.com/questions/58041676/how-to-pause-an-animation-in-swiftui