gpt4 book ai didi

ios - CABasicAnimation 比它设置的时间更快地到达它的端点

转载 作者:行者123 更新时间:2023-12-05 07:05:31 26 4
gpt4 key购买 nike

如果有人想尝试此代码,您只需 c+p 文件即可运行

实际上我在下面的代码中有 2 个问题。

1- 我有一个 Timer 和一个 CABasicAnimation,它们都在触发 longPressGesture 时运行。计时器是 15 秒,一旦我注意到这个问题,我决定用它来为动画计时。发生的事情是动画在计时器完成之前完成。动画将在计时器结束前和 CATransaction.setCompletionBlock()animationDidStop(_:finished) 被调用前 1 秒左右关闭/到达其端点。基本上动画结束得太早了。

2- 如果我将手指从按钮上移开,将调用 longPressGesture 的 .cancelled/.ended 并通过 pauseShapeLayerAnimation 在 invalidateTimer 中暂停计时器()。这是我发现真正停止动画的唯一方法。当我再次长按按钮时,我从头开始重新启动计时器和动画。问题是因为 pauseShapeLayerAnimation() 也会在计时器停止时调用(达到 15 秒)CATransaction.setCompletionBlock() 永远不会 animationDidStop(_:finished) 调用。只有当我将手指放回按钮上时,它们才会被调用。

更新 我通过检查 invalidateTimer 函数中的秒数是否为 != 0 解决了第二个问题

import UIKit

class ViewController: UIViewController {

//MARK:- UIElements
fileprivate lazy var roundButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.blue
return button
}()

fileprivate lazy var timerLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.monospacedDigitSystemFont(ofSize: 22, weight: .medium)
label.textColor = UIColor.black
label.text = initialStrForTimerLabel
label.textAlignment = .center
return label
}()

fileprivate lazy var box: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .brown
return view
}()

//MARK:- Properties
fileprivate let shapeLayer = CAShapeLayer()
fileprivate let bgShapeLayer = CAShapeLayer()
fileprivate var basicAnimation: CABasicAnimation!

fileprivate var maxTimeInSecs = 15
fileprivate lazy var seconds = maxTimeInSecs
fileprivate var milliseconds = 0
fileprivate lazy var timerStr = initialStrForTimerLabel
fileprivate lazy var initialStrForTimerLabel = "\(maxTimeInSecs).0"

fileprivate weak var timer: Timer?

//MARK:- View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white

setAnchors()

setGestures()
}

fileprivate var wereCAShapeLayersAdded = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

if !wereCAShapeLayersAdded {
wereCAShapeLayersAdded = true

roundButton.layer.cornerRadius = roundButton.frame.width / 2

addBothCAShapeLayersToRoundButton()
}
}

//MARK:- Animation Methods
fileprivate func addBothCAShapeLayersToRoundButton() {

bgShapeLayer.frame = box.bounds
bgShapeLayer.path = UIBezierPath(rect: box.bounds).cgPath
bgShapeLayer.strokeColor = UIColor.lightGray.cgColor
bgShapeLayer.fillColor = UIColor.clear.cgColor
bgShapeLayer.lineWidth = 6
box.layer.addSublayer(bgShapeLayer)
box.layer.insertSublayer(bgShapeLayer, at: 0)

shapeLayer.frame = box.bounds
shapeLayer.path = UIBezierPath(rect: box.bounds).cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 6
shapeLayer.lineCap = .round
shapeLayer.strokeEnd = 0

box.layer.addSublayer(shapeLayer)
}

fileprivate var isBasicAnimationAnimating = false
fileprivate func addProgressAnimation() {

CATransaction.begin()

basicAnimation = CABasicAnimation(keyPath: "strokeEnd")

removeAnimation()

if shapeLayer.timeOffset > 0.0 {

shapeLayer.speed = 1.0
shapeLayer.timeOffset = 0.0
}

basicAnimation.delegate = self
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
basicAnimation.fromValue = 0
basicAnimation.toValue = 1
basicAnimation.duration = CFTimeInterval(seconds)
basicAnimation.fillMode = CAMediaTimingFillMode.forwards
basicAnimation.isRemovedOnCompletion = false

CATransaction.setCompletionBlock {
print("CATransaction completion called\n")
}

shapeLayer.add(basicAnimation, forKey: "myAnimation")

CATransaction.commit()
}

fileprivate func removeAnimation() {
shapeLayer.removeAnimation(forKey: "myAnimation")
}

fileprivate func pauseShapeLayerAnimation() {

let pausedTime = shapeLayer.convertTime(CACurrentMediaTime(), from: nil)
shapeLayer.speed = 0.0
shapeLayer.timeOffset = pausedTime

print("animation has paused/stopped\n")
}

//MARK:- Anchors
fileprivate func setAnchors() {

view.addSubview(box)
view.addSubview(roundButton)
view.addSubview(timerLabel)

box.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 3).isActive = true
box.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 3).isActive = true
box.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -3).isActive = true
box.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -3).isActive = true

roundButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
roundButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
roundButton.widthAnchor.constraint(equalToConstant: 75).isActive = true
roundButton.heightAnchor.constraint(equalToConstant: 75).isActive = true

timerLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
timerLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
timerLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
}

//MARK:- CAAnimationDelegate
extension ViewController: CAAnimationDelegate {

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
print("***** animation done *****\n")
}
}

//MARK:- Timer Methods
extension ViewController {

fileprivate func startTimer() {

timer?.invalidate()

timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] _ in
self?.timerIsRunning()
})
}

@objc fileprivate func timerIsRunning() {

updateTimerLabel()

if !isBasicAnimationAnimating {
isBasicAnimationAnimating = true

addProgressAnimation()
}

milliseconds -= 1

if milliseconds < 0 {

milliseconds = 9

if seconds != 0 {
seconds -= 1

} else {

invalidateTimer()

print("timer done\n")
}
}

if milliseconds == 0 {

milliseconds = 0
}
}

fileprivate func updateTimerLabel() {

let millisecStr = "\(milliseconds)"
let secondsStr = seconds > 9 ? "\(seconds)" : "0\(seconds)"

timerLabel.text = "\(secondsStr).\(millisecStr)"
}

fileprivate func resetTimerSecsAndLabel() {

milliseconds = 0
seconds = maxTimeInSecs

timerLabel.text = initialStrForTimerLabel
}

fileprivate func invalidateTimer() {

if isBasicAnimationAnimating {
isBasicAnimationAnimating = false

if seconds != 0 {
pauseShapeLayerAnimation()
}
}

timer?.invalidate()
}
}

//MARK:- Gestures
extension ViewController {

fileprivate func setGestures() {

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGesture))
roundButton.addGestureRecognizer(tapRecognizer)

let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressGesture))
roundButton.addGestureRecognizer(longPressRecognizer)
}

@objc private func tapGesture(recognizer: UITapGestureRecognizer) {
print("tap\n")
}

@objc private func longPressGesture(recognizer: UILongPressGestureRecognizer) {

switch recognizer.state {
case .began:
resetTimerSecsAndLabel()
startTimer()
print("long gesture began\n")
case .ended, .cancelled:
invalidateTimer()
print("long gesture ended or cancelled\n")
case .failed:
print("long gesture failed\n")
default:
break
}
}
}

最佳答案

我认为动画提前完成是三个因素造成的错觉:

  1. 您正在使用 CAMediaTimingFunctionName.easeInEaseOut,这意味着绘图开始缓慢并且结束缓慢,因此很难判断绘图的真正结束。
  2. 绘图通过在线的起点绘制完成,这也使得很难准确看到绘图何时停止。
  3. 您的计时器应该从 更新标签之前的时间减去 0.1,因为当计时器首次更新时 0.1 已经过去。

当我将计时函数更改为 CAMediaTimingFunctionName.linear 并修复计时器时,它似乎总是在绘图完成时达到 0

关于ios - CABasicAnimation 比它设置的时间更快地到达它的端点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62711675/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com