Cómo crear un indicador de progreso circular para un temporizador de cuenta regresiva

Estoy intentando agregar una cuenta regresiva a mi aplicación y tengo problemas para animar esto. El aspecto que busco es algo similar a la cuenta regresiva del iPad que se muestra a continuación, con la barra roja aumentando a medida que el reloj cuenta hacia abajo. enter image description here

Inicialmente creé un atlas de imagen con una imagen individual por cada medio segundo de la cuenta atrás, pero parece que se necesita mucha memoria para ejecutar y, por lo tanto, bloquea la aplicación, así que ahora tengo que encontrar una alternativa.

He estado buscando colorear aquí https://developer.apple.com/library/prerelease/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Sprites/Sprites.html#//apple_ref/doc/uid/TP40013043-CH9 pero no se puede ver una forma de colorear una sección del sprite, parece que se cambiaría todo el sprite.

¿Alguien sabe si colorear sería una opción aquí, o hay una forma de reducir la memoria utilizada por un atlas de imagen?

Gracias

Puedes hacerlo usando CAShapeLayer y animando el final del trazo de la siguiente manera:

actualizar Xcode 9 • Swift 4

definir el tiempo restante

let timeLeftShapeLayer = CAShapeLayer() let bgShapeLayer = CAShapeLayer() var timeLeft: TimeInterval = 60 var endTime: Date? var timeLabel = UILabel() var timer = Timer() // here you create your basic animation object to animate the strokeEnd let strokeIt = CABasicAnimation(keyPath: "strokeEnd") 

definir un método para crear de UIBezierPath

startAngle at -90˚ y endAngle 270

 func drawBgShape() { bgShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius: 100, startAngle: -90.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: true).cgPath bgShapeLayer.strokeColor = UIColor.white.cgColor bgShapeLayer.fillColor = UIColor.clear.cgColor bgShapeLayer.lineWidth = 15 view.layer.addSublayer(bgShapeLayer) } func drawTimeLeftShape() { timeLeftShapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: view.frame.midX , y: view.frame.midY), radius: 100, startAngle: -90.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: true).cgPath timeLeftShapeLayer.strokeColor = UIColor.red.cgColor timeLeftShapeLayer.fillColor = UIColor.clear.cgColor timeLeftShapeLayer.lineWidth = 15 view.layer.addSublayer(timeLeftShapeLayer) } 

agrega tu etiqueta

 func addTimeLabel() { timeLabel = UILabel(frame: CGRect(x: view.frame.midX-50 ,y: view.frame.midY-25, width: 100, height: 50)) timeLabel.textAlignment = .center timeLabel.text = timeLeft.time view.addSubview(timeLabel) } 

en viewDidload establezca endTime y agregue su CAShapeLayer a su vista:

 override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(white: 0.94, alpha: 1.0) drawBgShape() drawTimeLeftShape() addTimeLabel() // here you define the fromValue, toValue and duration of your animation strokeIt.fromValue = 0 strokeIt.toValue = 1 strokeIt.duration = 60 // add the animation to your timeLeftShapeLayer timeLeftShapeLayer.add(strokeIt, forKey: nil) // define the future end time by adding the timeLeft to now Date() endTime = Date().addingTimeInterval(timeLeft) timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) } 

al actualizar el tiempo

 @objc func updateTime() { if timeLeft > 0 { timeLeft = endTime?.timeIntervalSinceNow ?? 0 timeLabel.text = timeLeft.time } else { timeLabel.text = "00:00" timer.invalidate() } } 

puede usar esta extensión para convertir los grados a radianes y tiempo de visualización

 extension TimeInterval { var time: String { return String(format:"%02d:%02d", Int(self/60), Int(ceil(truncatingRemainder(dividingBy: 60))) ) } } extension Int { var degreesToRadians : CGFloat { return CGFloat(self) * .pi / 180 } } 

Proyecto de muestra

Gran pedazo de código y respuesta! Solo pensé en sugerir lo siguiente. Estaba ganando tiempo mostrando como 1:00 y luego 0:60, lo que creo que podría deberse a la parte “ceil” del código.

Lo cambié a lo siguiente (también quería que los tiempos de menos de 60 se muestren de forma diferente) y parece que se eliminó la superposición de veces.

Por lo que vale …

 extension NSTimeInterval { var time:String { if self < 60 { return String(format: "%02d", Int(floor(self%60))) } else { return String(format: "%01d:%02d", Int(self/60), Int(floor(self%60))) } } }