Swift 倒计时按钮封装

作者: 梦里风吹过 | 来源:发表于2016-07-21 11:35 被阅读1126次

    参照网上的一个倒计时按钮,以学习的目的抄了一下,根据自己的理解修改了一些内容,使倒计时按钮用起来更方便了些。因为是几个月前写的,原作者和链接也找不着了,记得是在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)
        }
    }
    

    相关文章

      网友评论

      • 0dd7406fa64a:为什么我点击重新发送之后,倒计时的秒数就不正常了,方便加个QQ,我向你请教下吗
        梦里风吹过:923925522,很久之前写的了,倒计时不正常的话可以直接改改源码,倒计时是用计时器实现的,你可以改成GCD试试.之前发也是觉得可以在storyboard显示自定义属性比较好玩,就拿别人改了一下.
      • KennyHito:我用oc写的,https://github.com/NSLog-YuHaitao/iOS-CountDown
        0dd7406fa64a:为什么我点击重新发送之后,倒计时的秒数就不正常了,方便加个QQ,我向你请教下吗
        梦里风吹过:@YuHaitao 谢谢

      本文标题:Swift 倒计时按钮封装

      本文链接:https://www.haomeiwen.com/subject/nujbjttx.html