我已经获得了循环进度视图的代码,它显示了您通过计时器的距离的进度,例如:您将计时器设置为60秒,当您达到30秒时,进度条处于中途。代码如下所示:
import UIKit
class TimerCircularProgressView: UIView {
@IBInspectable var timeSeconds:CGFloat = CGFloat(timers.seconds[timers.timerID]) {
didSet {
setNeedsDisplay()
}
}
@IBInspectable var timeLabelString:String = "" {
didSet {
setNeedsDisplay()
}
}
private var startAngle = CGFloat(-90 * Double.pi / 180)
private var endAngle = CGFloat(270 * Double.pi / 180)
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
backgroundColor = .clear
}
override func draw(_ rect: CGRect) {
// General Declarations
guard let context = UIGraphicsGetCurrentContext() else {
return
}
// Colour Declarations
let progressColour = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
let progressBackgroundColour = UIColor(red: 1, green: 1, blue: 1, alpha: 0.4)
// let titleColour = UIColor(red: 1, green: 1, blue: 1, alpha: 0.8) // let titleColour = UIColor(red: 1, green: 1, blue: 1, alpha: 0.8)
// Shadow Declarations
let innerShadow = UIColor.black.withAlphaComponent(0.22)
let innerShadowOffset = CGSize(width: 3.1, height: 3.1)
let innerShadowBlurRadius = CGFloat(4)
// Background Drawing
let backgroundPath = UIBezierPath(ovalIn: CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height))
backgroundColor?.setFill()
backgroundPath.fill()
// ProgressBackground Drawing
let progressPadding = CGFloat(timers.deviceWidth / 32 * 1.5) // 15
print(progressPadding)
print(timers.deviceWidth)
let progressBackgroundPath = UIBezierPath(ovalIn: CGRect(x: rect.minX + progressPadding/2, y: rect.minY + progressPadding/2, width: rect.size.width - progressPadding, height: rect.size.height - progressPadding))
progressBackgroundColour.setStroke()
progressBackgroundPath.lineWidth = timers.deviceWidth / 32 // 5, 10
progressBackgroundPath.stroke()
// Progress Drawing
let progressRect = CGRect(x: rect.minX + progressPadding/2, y: rect.minY + progressPadding/2, width: rect.size.width - progressPadding, height: rect.size.height - progressPadding)
let progressPath = UIBezierPath()
progressPath.addArc(withCenter: CGPoint(x: progressRect.midX, y: progressRect.midY), radius: progressRect.width / 2, startAngle: startAngle, endAngle: (endAngle - startAngle) * (CGFloat(timers.seconds[timers.timerID]/timers.seconds[timers.timerID]) - (timeSeconds / CGFloat(timers.seconds[timers.timerID]))) + startAngle, clockwise: true)
progressColour.setStroke()
progressPath.lineWidth = timers.deviceWidth / 32 * 0.8 // Thickness of the progress line
progressPath.lineCapStyle = CGLineCap.round
progressPath.stroke()
// Text Drawing
let textRect = CGRect(x: rect.minX, y: rect.minY, width: rect.size.width, height: rect.size.height)
let textContent = NSString(string: timeLabelString)
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
textStyle.alignment = .center
let textFontAttributes = [
NSAttributedStringKey.font: UIFont(name: "Helvetica", size: rect.width / timers.timerFontSize)!, // 3, HelveticaNeue-Light
NSAttributedStringKey.foregroundColor: UIColor.black, // NSAttributedStringKey.foregroundColor: titleColor,
NSAttributedStringKey.paragraphStyle: textStyle]
let textHeight = textContent.boundingRect(with: CGSize(width: textRect.width, height: textRect.height), options: .usesLineFragmentOrigin, attributes: textFontAttributes, context: nil).height
context.saveGState()
context.clip(to: textRect)
textContent.draw(in: CGRect(x: textRect.minX, y: textRect.minY + (textRect.height - textHeight) / 2, width: textRect.width, height: textHeight), withAttributes: textFontAttributes)
context.restoreGState();
}
}
目前,循环进度条每秒更新一次,这意味着在短时间内,进度部分移动的动画是跳跃的,而不是平滑的。
我已经尝试设置一个大约0.05秒间隔的计时器,但这可能会给出不准确的时间,所以我尝试了0.1和0.2,它仍然显得相当跳跃。
@objc func processSlider() {
if secondsDDecimal > 0 {
secondsDDecimal -= 0.2
}
circularProgressView.timeSeconds = CGFloat(secondsDDecimal)
}
有没有办法让循环进度视图的转换逐秒动画,以便它是平滑的而不是跳跃的?
答案 0 :(得分:0)
前段时间我创建了SRCountdownTimer,它完全符合您的目标。这就是我在那里实现计时器的方式:
private let fireInterval: TimeInterval = 0.01 // ~60 FPS
/**
* Starts the timer and the animation. If timer was previously runned, it'll invalidate it.
* - Parameters:
* - beginingValue: Value to start countdown from.
*/
public func start(beginingValue: Int) {
totalTime = TimeInterval(beginingValue)
elapsedTime = 0
timer?.invalidate()
timer = Timer(timeInterval: fireInterval, repeats: true) { [unowned self] _ in
self.elapsedTime += self.fireInterval
if self.elapsedTime < self.totalTime {
self.setNeedsDisplay() // Will invoke "draw" method to update the progress view
} else {
self.end()
}
}
RunLoop.main.add(timer!, forMode: .defaultRunLoopMode)
delegate?.timerDidStart?()
}
所以基本上每次计时器触发时我都会将激发间隔值添加到我的 elapsedTime 。这使得倒计时能够以60 FPS顺利进行。
在评论中建议使用日期对象中的 timeIntervalSinceNow 方法,通过从当前值中减去计时器的开始时间戳来计算已用时间。尽管此选项似乎可以为您提供更准确的结果,但我不建议您使用它。
原因在于,在这种情况下,您将在每次计时器触发时计算当前时间戳,这意味着每0.01秒(!)。这是一个过于复杂的解决方案,并且更加聪明的是接受与100%相同的计时器间隔时间间隔的交易(但差异足够小以至于用户不明显)。
答案 1 :(得分:0)
你实际上并不需要计时器。如果您使用CAShapeLayer,您可以jsut为strokeStart或strokeEnd设置动画,动画系统将为您处理每个标志。我在github上有一个示例游乐场,或者您可以在下面复制并粘贴:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
class AnimatedRingView: UIView {
private static let animationDuration = CFTimeInterval(2)
private let π = CGFloat.pi
private let startAngle = 1.5 * CGFloat.pi
private let strokeWidth = CGFloat(8)
var proportion = CGFloat(0.5) {
didSet {
setNeedsLayout()
}
}
private lazy var circleLayer: CAShapeLayer = {
let circleLayer = CAShapeLayer()
circleLayer.strokeColor = UIColor.gray.cgColor
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.lineWidth = self.strokeWidth
self.layer.addSublayer(circleLayer)
return circleLayer
}()
private lazy var ringlayer: CAShapeLayer = {
let ringlayer = CAShapeLayer()
ringlayer.fillColor = UIColor.clear.cgColor
ringlayer.strokeColor = self.tintColor.cgColor
ringlayer.lineCap = kCALineCapRound
ringlayer.lineWidth = self.strokeWidth
self.layer.addSublayer(ringlayer)
return ringlayer
}()
override func layoutSubviews() {
super.layoutSubviews()
let radius = (min(frame.size.width, frame.size.height) - strokeWidth - 2)/2
let circlePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: startAngle + 2 * π, clockwise: true)
circleLayer.path = circlePath.cgPath
ringlayer.path = circlePath.cgPath
ringlayer.strokeEnd = proportion
}
func animateRing(From startProportion: CGFloat, To endProportion: CGFloat, Duration duration: CFTimeInterval = animationDuration) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = startProportion
animation.toValue = endProportion
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
ringlayer.strokeEnd = endProportion
ringlayer.strokeStart = startProportion
ringlayer.add(animation, forKey: "animateRing")
}
}
let view = AnimatedRingView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
PlaygroundPage.current.liveView = view
view.animateRing(From: 0, To: 1)
PlaygroundPage.current.needsIndefiniteExecution = true