How to pause an animation in SwiftUI?

天大地大妈咪最大 提交于 2021-02-10 20:31:09

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!