Swift 倒计时按钮封装
参照网上的一个倒计时按钮,以学习的目的抄了一下,根据自己的理解修改了一些内容,使倒计时按钮用起来更方便了些。因为是几个月前写的,原作者和链接也找不着了,记得是在CocoaChina上找到的。这篇文章只为分享和学习。
1.先说思路
思路很简单,创建一个UIButton,重写构造器方法,在实例上添加一个UIlabel,然后利用计时器在label上显示倒计时数字。这样做的原因是如果直接设置button的title数字切换时文字会闪一下才更新。然后可以根据需要给label添加动画。本篇大部分代码都是抄的,里面的旋转动画和放大动画占了很大的代码篇幅,虽然我觉得两个动画都不太实用,也从来没用过。
2.不多说,贴代码,逻辑还是很简单的
import UIKit
/**
动画类型
*/
enum CountDownAnimationType {
case None //没有动画
case Scale //缩放动画
case Rotate //旋转动画
}
/// 自定义倒计时按钮
@IBDesignable class CountDownBtn: UIButton {
/// 倒计时中的背景颜色
@IBInspectable var enabled_bgColor: UIColor = UIColor.clearColor()
/// 倒计时时数字颜色
@IBInspectable var numberColor :UIColor = UIColor.blackColor(){
didSet{
timeLabel.textColor = numberColor
}
}
/// 时间长度(秒)
@IBInspectable var count :Int = 0 {
didSet{
startCount = count
originNum = count
}
}
/// 动画类型,默认没有动画
var animaType: CountDownAnimationType = CountDownAnimationType.None
override var frame: CGRect {
set{
super.frame = newValue
timeLabel.frame = frame
}
get{
return super.frame
}
}
override var backgroundColor: UIColor?{
set{
super.backgroundColor = newValue
if normalBgColor == nil {
normalBgColor = backgroundColor
}
}
get{
return super.backgroundColor
}
}
private var btnTitle :String?
private var normalBgColor: UIColor?
private var timer: NSTimer!
private var startCount = 0
private var originNum = 0
//倒计时Label
private var timeLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
btnTitle = self.titleForState(.Normal)
self.addLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
btnTitle = self.titleForState(.Normal)
self.addLabel()
}
private func addLabel() {
timeLabel.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))
timeLabel.backgroundColor = UIColor.clearColor()
timeLabel.font = UIFont.systemFontOfSize(17)//在开启定时器时不排除存储在设置字号失败的情况
timeLabel.textAlignment = NSTextAlignment.Center
timeLabel.textColor = numberColor
timeLabel.text = ""
self.addSubview(timeLabel)
}
/**
开启倒计时
*/
func startCountDown() {
//设置为按钮字号在addLabel()会失败
timeLabel.font = self.titleLabel?.font
timeLabel.text = "\(self.originNum)秒"
self.setTitle("", forState: .Normal)
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(CountDownBtn.countDown), userInfo: nil, repeats: true)
self.backgroundColor = enabled_bgColor
self.enabled = false
switch self.animaType {
case .Scale:
self.numAnimation()
case .Rotate:
self.rotateAnimation()
default:
return
}
}
// 倒计时开始
@objc private func countDown() {
self.startCount -= 1
timeLabel.text = "\(self.startCount)秒"
//倒计时完成后停止定时器,移除动画
if self.startCount <= 0 {
if self.timer == nil {
return
}
self.setTitle(btnTitle, forState: .Normal)
timeLabel.layer.removeAllAnimations()
timeLabel.text = ""
self.timer.invalidate()
self.timer = nil
self.enabled = true
self.startCount = self.originNum
self.backgroundColor = normalBgColor
}
}
//放大动画
private func numAnimation() {
let duration: CFTimeInterval = 1
let beginTime = CACurrentMediaTime()
// Scale animation
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.keyTimes = [0, 0.5, 1]
scaleAnimation.values = [1, 1.5, 2]
scaleAnimation.duration = duration
// Opacity animation
let opacityAnimaton = CAKeyframeAnimation(keyPath: "opacity")
opacityAnimaton.keyTimes = [0, 0.5, 1]
opacityAnimaton.values = [1, 0.5, 0]
opacityAnimaton.duration = duration
// Animation
let animation = CAAnimationGroup()
animation.animations = [scaleAnimation, opacityAnimaton]
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = duration
animation.repeatCount = HUGE
animation.removedOnCompletion = false
animation.beginTime = beginTime
timeLabel.layer.addAnimation(animation, forKey: "animation")
self.layer.addSublayer(timeLabel.layer)
}
// 旋转变小动画
private func rotateAnimation() {
let duration: CFTimeInterval = 1
let beginTime = CACurrentMediaTime()
// Rotate animation
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.fromValue = NSNumber(int: 0)
rotateAnimation.toValue = NSNumber(double: M_PI * 2)
rotateAnimation.duration = duration;
// Scale animation
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.keyTimes = [0]
scaleAnimation.values = [1, 2]
scaleAnimation.duration = 0
// Opacity animation
let opacityAnimaton = CAKeyframeAnimation(keyPath: "opacity")
opacityAnimaton.keyTimes = [0, 0.5]
opacityAnimaton.values = [1, 0]
opacityAnimaton.duration = duration
// Scale animation
let scaleAnimation2 = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation2.keyTimes = [0, 0.5]
scaleAnimation2.values = [2, 0]
scaleAnimation2.duration = duration
let animation = CAAnimationGroup()
animation.animations = [rotateAnimation, scaleAnimation, opacityAnimaton, scaleAnimation2]
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = duration
animation.repeatCount = HUGE
animation.removedOnCompletion = false
animation.beginTime = beginTime
timeLabel.layer.addAnimation(animation, forKey: "animation")
self.layer.addSublayer(timeLabel.layer)
}
}
这里简单说下@IBDesignable和@IBInspectable,@IBDesignable标记类,@IBInspectable标记属性,可以使这个类的属性在xib和storyboard设置面板中进行设置,配合didSet可以直接在xib和storyboard面板中显示实时效果。
3.使用
这里只展示下在storyBoard里的用法
a.把代码放入工程中。
b.拖一个UIButton,修改父类为CountDownBtn。
c.设置相关属性
按钮的默认标题,字号,普通状态下按钮文字颜色是在Button里设置的,顶部三个属性分别为倒计时状态下按钮背景色,倒计时状态下文字颜色,倒计时的时间设置。
storyboard设置示例
d.点击事件
@IBAction func getCodeAction(sender: CountDownBtn) {
// 设置动画类型,可以不写,默认没有动画效果
sender.animaType = .Rotate
// 开启倒计时
sender.startCountDown()
// 这里写其它操作,如获取验证码
}
这里注意sender的类型,或者自己进行转换。
最后
整个代码逻辑很简单,可以根据需要定制下代码。另外里面的逻辑也可以通过OC实现,didSet方法应该可以用KVO代替,或者可以使用set方法(我也不确定)。另外,@IBDesignable和@IBInspectable在OC上的替代方式如下:
IB_DESIGNABLE
@interface CustomButton : UIButton
@property (nonatomic, strong) IBInspectable UIImage *highlightSelectedImage;
@end
如果有谁用OC写了请分享我一份😊。
有什么地方做的不好,欢迎指出。相互学习才能更快进步。
Demo
Swift4.0更新,没上传,自用
import UIKit
/**
动画类型
*/
enum CountDownAnimationType {
case None //没有动画
case Scale //缩放动画
case Rotate //旋转动画
}
/// 自定义倒计时按钮
@IBDesignable class CountDownBtn: UIButton {
/// 倒计时中的背景颜色
@IBInspectable var enabled_bgColor: UIColor = UIColor.clear
/// 倒计时时数字颜色
@IBInspectable var numberColor :UIColor = UIColor.black{
didSet{
timeLabel.textColor = numberColor
}
}
/// 时间长度(秒)
@IBInspectable var count :Int = 0 {
didSet{
startCount = count
originNum = count
}
}
/// 动画类型,默认没有动画
var animaType: CountDownAnimationType = CountDownAnimationType.None
override var frame: CGRect {
set{
super.frame = newValue
timeLabel.frame = frame
}
get{
return super.frame
}
}
override var backgroundColor: UIColor?{
set{
super.backgroundColor = newValue
if normalBgColor == nil {
normalBgColor = backgroundColor
}
}
get{
return super.backgroundColor
}
}
private var btnTitle :String?
private var normalBgColor: UIColor?
private var timer: Timer!
private var startCount = 0
private var originNum = 0
//倒计时Label
private var timeLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
btnTitle = self.title(for: .normal)
self.addLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
btnTitle = self.title(for: .normal)
self.addLabel()
}
private func addLabel() {
timeLabel.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
//CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))
timeLabel.backgroundColor = UIColor.clear
timeLabel.font = UIFont.systemFont(ofSize: 17)//在开启定时器时不排除存储在设置字号失败的情况
timeLabel.textAlignment = NSTextAlignment.center
timeLabel.textColor = numberColor
timeLabel.text = ""
self.addSubview(timeLabel)
}
/**
开启倒计时
*/
func startCountDown() {
//设置为按钮字号在addLabel()会失败
timeLabel.font = self.titleLabel?.font
timeLabel.text = "\(self.originNum)秒"
self.setTitle("", for: .normal)
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(CountDownBtn.countDown), userInfo: nil, repeats: true)
self.backgroundColor = enabled_bgColor
self.isEnabled = false
switch self.animaType {
case .Scale:
self.numAnimation()
case .Rotate:
self.rotateAnimation()
default:
return
}
}
// 倒计时开始
@objc private func countDown() {
self.startCount -= 1
timeLabel.text = "\(self.startCount)秒"
//倒计时完成后停止定时器,移除动画
if self.startCount <= 0 {
if self.timer == nil {
return
}
self.setTitle(btnTitle, for: .normal)
timeLabel.layer.removeAllAnimations()
timeLabel.text = ""
self.timer.invalidate()
self.timer = nil
self.isEnabled = true
self.startCount = self.originNum
self.backgroundColor = normalBgColor
}
}
//放大动画
private func numAnimation() {
let duration: CFTimeInterval = 1
let beginTime = CACurrentMediaTime()
// Scale animation
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.keyTimes = [0, 0.5, 1]
scaleAnimation.values = [1, 1.5, 2]
scaleAnimation.duration = duration
// Opacity animation
let opacityAnimaton = CAKeyframeAnimation(keyPath: "opacity")
opacityAnimaton.keyTimes = [0, 0.5, 1]
opacityAnimaton.values = [1, 0.5, 0]
opacityAnimaton.duration = duration
// Animation
let animation = CAAnimationGroup()
animation.animations = [scaleAnimation, opacityAnimaton]
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = duration
animation.repeatCount = HUGE
animation.isRemovedOnCompletion = false
animation.beginTime = beginTime
timeLabel.layer.add(animation, forKey: "animation")
self.layer.addSublayer(timeLabel.layer)
}
// 旋转变小动画
private func rotateAnimation() {
let duration: CFTimeInterval = 1
let beginTime = CACurrentMediaTime()
// Rotate animation
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.fromValue = NSNumber(value: 0)
rotateAnimation.toValue = NSNumber(value: Double.pi * 2)
rotateAnimation.duration = duration;
// Scale animation
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.keyTimes = [0]
scaleAnimation.values = [1, 2]
scaleAnimation.duration = 0
// Opacity animation
let opacityAnimaton = CAKeyframeAnimation(keyPath: "opacity")
opacityAnimaton.keyTimes = [0, 0.5]
opacityAnimaton.values = [1, 0]
opacityAnimaton.duration = duration
// Scale animation
let scaleAnimation2 = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation2.keyTimes = [0, 0.5]
scaleAnimation2.values = [2, 0]
scaleAnimation2.duration = duration
let animation = CAAnimationGroup()
animation.animations = [rotateAnimation, scaleAnimation, opacityAnimaton, scaleAnimation2]
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = duration
animation.repeatCount = HUGE
animation.isRemovedOnCompletion = false
animation.beginTime = beginTime
timeLabel.layer.add(animation, forKey: "animation")
self.layer.addSublayer(timeLabel.layer)
}
}