之前在cocoaChina还是哪看到一个OC版的(具体忘记了,时间有点久了,最近在自学swift,这个小Demo拿来练手,也算是记录,今后自己需要,找起来也方便。如有侵权,请留言联系)
带进度按钮实现代码如下(Demo就不留下了,所有代码都已贴上)
import UIKit
class CountDownView: UIView {
// MARK:- 属性定义
var startBack: (() -> ())?
var completeBack: (() -> ())?
var progressLineWidth: CGFloat?
var progressLineColor: UIColor?
var countLb: UILabel?
var totalTimes: NSInteger = 0
var isCountDown:Bool = false
// MARK:- 懒加载
fileprivate lazy var progressLayer:CAShapeLayer = {
let progress = CAShapeLayer()
let kWidth = self.frame.size.width
let kHeight = self.frame.size.height
progress.frame = CGRect(x: 0, y: 0, width: kWidth, height: kHeight)
progress.position = CGPoint(x: kWidth/2.0, y: kHeight/2.0)
let point = CGPoint(x: kWidth/2.0, y: kHeight/2.0)
progress.path = UIBezierPath(arcCenter: point, radius: (kWidth - self.progressLineWidth!)/2.0, startAngle: 0, endAngle: CGFloat(M_PI * 2), clockwise: true).cgPath
progress.fillColor = UIColor.clear.cgColor
progress.lineWidth = self.progressLineWidth!
progress.strokeColor = self.progressLineColor!.cgColor
progress.strokeEnd = 0
progress.strokeStart = 0
progress.lineCap = kCALineCapRound
return progress
}()
fileprivate lazy var rotateAnimation:CABasicAnimation = {
let rotateAnima = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnima.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
rotateAnima.fromValue = CGFloat(2 * M_PI)
rotateAnima.toValue = CGFloat(0)
rotateAnima.duration = CFTimeInterval(self.totalTimes)
rotateAnima.isRemovedOnCompletion = false
return rotateAnima
}()
fileprivate lazy var strokeAnimationEnd:CABasicAnimation = {
let strokeAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
strokeAnimation.duration = CFTimeInterval(self.totalTimes)
strokeAnimation.fromValue = CGFloat(1)
strokeAnimation.toValue = CGFloat(0)
strokeAnimation.speed = Float(1.0)
strokeAnimation.isRemovedOnCompletion = false
return strokeAnimation
}()
fileprivate lazy var animationGroup:CAAnimationGroup = {
let group = CAAnimationGroup()
group.animations = [self.strokeAnimationEnd, self.rotateAnimation]
group.duration = CFTimeInterval(self.totalTimes)
return group
}()
/**
* 初始化定时器
frame 位置
totalTime 总时间
lineWidth 线宽
lineColor 线色
fontSize 字体大小(控件的宽度/多少)
startFinishCallback 开始闭包
completeFinishCallback 完成闭包
*/
init(frame: CGRect, totalTime: NSInteger, lineWidth:CGFloat, lineColor:UIColor, textFontSize:CGFloat, startFinishCallback:@escaping () -> (), completeFinishCallback:@escaping () -> ()) {
super.init(frame: frame)
startBack = startFinishCallback
completeBack = completeFinishCallback
progressLineWidth = lineWidth
progressLineColor = lineColor
totalTimes = totalTime
layer.addSublayer(progressLayer)
createLabel(textFontSize: textFontSize)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK:- 逻辑处理
extension CountDownView {
/** 创建label */
fileprivate func createLabel(textFontSize: CGFloat) {
let label = UILabel()
label.frame = self.bounds
label.textColor = self.progressLineColor
label.font = UIFont.systemFont(ofSize: self.frame.size.width / textFontSize)
label.textAlignment = .center
addSubview(label)
countLb = label
}
/** 创建定时器 */
fileprivate func createTimer() {
weak var weakSelf = self
if totalTimes > 0 {
var timeout = totalTimes
// 1、获取一个全局队列
let queue = DispatchQueue.global()
// 2、创建间隔定时器
let time = DispatchSource.makeTimerSource(flags: [], queue: queue)
time.scheduleRepeating(deadline: .now(), interval: .seconds(1), leeway: .microseconds(100))
time.setEventHandler(handler: {
if timeout <= 0 {
// 2.1定时器取消
time.cancel()
// 2.2主线程刷新UI
DispatchQueue.main.async(execute: {
weakSelf?.isCountDown = false
weakSelf?.isHidden = true
weakSelf?.stopCountDown()
})
}else {
weakSelf?.isCountDown = true
let seconds = timeout % 60
DispatchQueue.main.async(execute: {
self.countLb?.text = "\(String(seconds))s"
})
timeout -= 1
}
})
// 3、复原定时器
time.resume()
}
}
/** 开始倒计时 */
func startTheCountDown(){
self.isHidden = false
isCountDown = true
if (startBack != nil) {
startBack!()
}
createTimer()
progressLayer.add(animationGroup, forKey: "group")
}
/** 停止倒计时 */
fileprivate func stopCountDown(){
progressLayer.removeAllAnimations()
if completeBack != nil {
completeBack!()
}
}
}
VC中调用(PS:其中运用到了PKHUD,如需使用,请cocoapods自行添加)
import UIKit
import PKHUD
/** 倒计时总时间 */
private let kCountdownTime:Int = 8
/** 线宽 */
private let kLineWidth:CGFloat = 2.0
class ViewController: UIViewController {
/** 带进度按钮计时器 */
var countView: CountDownView?
/** 普通按钮计时器 */
fileprivate lazy var normalButton:UIButton = {
let button = UIButton()
button.titleLabel?.font = UIFont.systemFont(ofSize: 20)
button.layer.cornerRadius = 5
button.layer.shadowOffset = CGSize(width: 5, height: 5)
button.layer.shadowOpacity = 0.8
button.layer.shadowColor = UIColor.black.cgColor
button.frame = CGRect(x: (self.view.frame.size.width - 200) / 2.0, y: 200, width: 200, height: 40)
button.backgroundColor = UIColor.purple
button.setTitle("获取验证码", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(normalButtonClick), for: .touchUpInside)
return button
}()
/** 剩余时间 */
var remainingTime: Int = 0{
willSet{
normalButton.setTitle("\(newValue)s后重新获取", for: .normal)
if newValue <= 0 {
normalButton.setTitle("重新获取", for: .normal)
isCounting = false
}
}
}
/** 计时器 */
var timer: Timer?
/** 按钮开关 */
var isCounting:Bool = false {
willSet {
if newValue {
/** 初始化计时器 */
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
remainingTime = kCountdownTime
normalButton.backgroundColor = UIColor.gray
}else {
// 暂停且销毁计时器
timer?.invalidate()
timer = nil
normalButton.backgroundColor = UIColor.red
}
normalButton.isEnabled = !newValue
}
}
// MARK:- 系统回调
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
}
// MARK:- 设置UI
extension ViewController {
fileprivate func setupUI() {
view.backgroundColor = UIColor.white
view.addSubview(normalButton)
let progressBut = UIButton()
progressBut.frame = CGRect(x: (view.frame.size.width - 200) / 2.0, y: 400, width: 200, height: 40)
progressBut.titleLabel?.font = UIFont.systemFont(ofSize: 20)
progressBut.setTitle("获取验证码", for: .normal)
progressBut.layer.cornerRadius = 5
progressBut.layer.shadowOpacity = 0.7
progressBut.layer.shadowColor = UIColor.black.cgColor
progressBut.layer.shadowOffset = CGSize(width: 5, height: 5)
progressBut.backgroundColor = UIColor.red
progressBut.setTitleColor(UIColor.white, for: .normal)
progressBut.addTarget(self, action: #selector(progressButtonCilck), for: .touchUpInside)
view.addSubview(progressBut)
let viewFrame = CGRect(x: (view.frame.size.width - 20) / 2.0, y: 400, width: 30, height: 30)
let countDownView = CountDownView(frame: viewFrame, totalTime: kCountdownTime, lineWidth: kLineWidth, lineColor: UIColor.red, textFontSize: 2, startFinishCallback: {
progressBut.isHidden = true
}, completeFinishCallback: {
progressBut.isHidden = false
progressBut.setTitle("重新获取", for: .normal)
})
view.addSubview(countDownView)
countView = countDownView
}
}
// MARK:- 事件处理
extension ViewController {
func normalButtonClick(){
HUD.flash(.labeledSuccess(title: "", subtitle: "发送成功,请注意查收"), delay: 1.0)
isCounting = true
}
func updateTime(timer: Timer){
remainingTime -= 1
}
//--------------------分割线-----------------------//
func progressButtonCilck(){
HUD.flash(.labeledSuccess(title: "", subtitle: "发送成功,请注意查收"), delay: 1.0)
countView?.startTheCountDown()
}
}
总结:
普通的按钮计时就不讲了,就是运用属性监听器:willSet(在新的值在改变之前调用)。带进度条的按钮逻辑理顺之后还是比较简单的,主要利用 闭包控制显示隐藏 + 动画组合 + 间隔定时器 来实现(在实现“** createTimer**”方法时候,费了我一点时间,swift3.0在此改动有点大,主要还是基础不扎实)