之前在cocoaChina還是哪看到一個OC版的(具體忘記了,時間有點久了,最近在自學(xué)swift,這個小Demo拿來練手,也算是記錄,今后自己需要,找起來也方便。如有侵權(quán),請留言聯(lián)系)
帶進度按鈕實現(xiàn)代碼如下(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 {
/** 創(chuàng)建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
}
/** 創(chuàng)建定時器 */
fileprivate func createTimer() {
weak var weakSelf = self
if totalTimes > 0 {
var timeout = totalTimes
// 1、獲取一個全局隊列
let queue = DispatchQueue.global()
// 2、創(chuàng)建間隔定時器
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、復(fù)原定時器
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中調(diào)用(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?
/** 按鈕開關(guān) */
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:- 系統(tǒng)回調(diào)
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
}
// MARK:- 設(shè)置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: "發(fā)送成功,請注意查收"), delay: 1.0)
isCounting = true
}
func updateTime(timer: Timer){
remainingTime -= 1
}
//--------------------分割線-----------------------//
func progressButtonCilck(){
HUD.flash(.labeledSuccess(title: "", subtitle: "發(fā)送成功,請注意查收"), delay: 1.0)
countView?.startTheCountDown()
}
}
總結(jié):
普通的按鈕計時就不講了,就是運用屬性監(jiān)聽器:willSet(在新的值在改變之前調(diào)用)。帶進度條的按鈕邏輯理順之后還是比較簡單的,主要利用 閉包控制顯示隱藏 + 動畫組合 + 間隔定時器 來實現(xiàn)(在實現(xiàn)“** createTimer**”方法時候,費了我一點時間,swift3.0在此改動有點大,主要還是基礎(chǔ)不扎實)