Swift学习笔记(十一):倒计时进度条

213 阅读1分钟

自定义控件的实现

import UIKit

fileprivate let startAngle = CGFloat(Double.pi / -2.0)

class CircleProgressView: UIView {

    typealias FinishBlock = ()->Void

    var finishBlock: FinishBlock?


    private lazy var bgLayer: CAShapeLayer = CAShapeLayer()

    private lazy var progressLayer: CAShapeLayer = CAShapeLayer()

    private lazy var timeLabel: UILabel = UILabel()

    private lazy var timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global())

    private var duration: Double = 0

    private var progress: Double = 0


    convenience init(frame: CGRect, duration: Double) {

        self.init(frame: frame)

        self.duration = duration

        setupView()

    }
    

    override init(frame: CGRect) {

        super.init(frame: frame)

    }


    required init?(coder: NSCoder) {

        fatalError("init(coder:) has not been implemented")

    }


    func setupView() {


        // fillColor 用于背景颜色填充, strokeColor 用于线条颜色

        bgLayer.fillColor = UIColorHex(hex: 0xFFFFFF, alpha: 0.25).cgColor

        layer.addSublayer(bgLayer)
        

        progressLayer.fillColor = nil

        progressLayer.strokeColor = UIColorHex(hex: 0xFFFFFF).cgColor

        progressLayer.lineWidth = 4.0

        layer.addSublayer(progressLayer)
        

        timeLabel.textColor = .white

        timeLabel.text = "\(Int(duration))s"

        addSubview(timeLabel)


    }

    

    // 布局 resize 时会触发该方法

    override func layoutSubviews() {

        super.layoutSubviews()


        let radius = bounds.height / 2.0

        let endAngle = startAngle + CGFloat(Double.pi * 2 * progress)


        // 画圆/扇形

        bgLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath

        // 画圆弧

        progressLayer.path = UIBezierPath(arcCenter: CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0),

                                          radius: radius,

                                          startAngle: startAngle,

                                          endAngle: endAngle,

                                          clockwise: true).cgPath

        

        timeLabel.translatesAutoresizingMaskIntoConstraints = false

        timeLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true

        timeLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true

    }

    

    public func updateWithProgress(currentProgress: Double, animated: Bool) {

        progress = currentProgress

        if animated {

            let animation = CABasicAnimation(keyPath: "strokeEnd")

            animation.duration = duration

            animation.fromValue = 0

            animation.toValue = 1

            progressLayer.add(animation, forKey: nil)

        }

        

        timer.schedule(deadline: .now(), repeating: .seconds(1))

        timer.setEventHandler(handler: {

            DispatchQueue.main.async { [weak self] in

                if self!.duration <= 0 {

                    self?.timer.cancel()

                    self?.finishBlock?()

                }

                self!.duration -= 1

                

                let textDuration = (self?.duration ?? 0) + 1

                self?.timeLabel.text = "\(Int(textDuration))s"

            }

        })

        timer.resume()

    }

}

使用

import UIKit

class AdvertiseViewController: BaseViewController {

    var circleProgressView = CircleProgressView()

    override func viewDidLoad() {
        super.viewDidLoad()

        circleProgressView = CircleProgressView(frame: CGRect(x: SCREEN_W - 20 - 50, y: STATUS_BAR_H, width: 50, height: 50), duration: 5)

        view.addSubview(circleProgressView)

        circleProgressView.updateWithProgress(currentProgress: 1, animated: true)
        

        weak var weakself = self

        circleProgressView.finishBlock = {

            weakself?.switchRootController()

        }

    }

    func switchRootController() {

    }

}