Swift 进度条

  水波纹进度

Posted by JT on August 25, 2017
import UIKit

enum EWSWaveShapModel {
    case circle
    case rect
}

class EWSWaveLoadingView: UIView {
    
    var originX = 0.0 //X坐标起点, the x origin of wave
    var shapeModel : EWSWaveShapModel = .circle
    
    fileprivate let cycle = 1.0                     //循环次数
    fileprivate let waveMoveSpan = 10.0             //波浪移动单位跨度
    fileprivate let animationUnitTime = 0.05        //重画单位时间
    
    fileprivate var term = 60.0                     //周期(在代码中重新计算)
    fileprivate var phasePosition = 0.0             //相位必须为0(画曲线机制局限)
    fileprivate var amplitude = 10.0                //波幅
    fileprivate var position = 40.0                 //X轴所在的Y坐标(在代码中重新计算)
    
    fileprivate let heavyColor = EWSRGBCOLOR(R: 38, G: 227, B: 198)  //波浪重度颜色
    fileprivate let lightColor = EWSRGBCOLOR(R: 121, G: 248, B: 221) //波浪轻度颜色
    fileprivate let clipCircleColor = EWSRGBCOLOR(R: 232, G: 233, B: 231)  //外圈颜色
    fileprivate var clipCircleLineWidth: CGFloat = 2 //外圈宽度
    fileprivate let progressTextFontSize: CGFloat = 15.0  //文字大小
    
    fileprivate var waving: Bool = true
    fileprivate let waveLable = UILabel()
    
    var progress: Double = 0.5 {
        didSet {
            self.setNeedsDisplay()
        }
    }
    
    var waveAmplitude: Double {
        get {
            return amplitude
        }
        set {
            amplitude = newValue
            self.setNeedsDisplay()
        }
    }
    
    var borderWidth: CGFloat {
        get {
            return clipCircleLineWidth
        }
        set {
            clipCircleLineWidth = newValue
            self.setNeedsDisplay()
        }
    }
    
    var attributeString: NSAttributedString? {
        didSet {
            if let attributeStr = attributeString {
                waveLable.attributedText = attributeStr
            }
        }
    }
    
    //if use not in xib, create an func init
    override func awakeFromNib() {
        animationWave()
        self.backgroundColor = lightColor
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        buildWaveLoadingSubView()
        animationWave()
        self.backgroundColor = lightColor
    }
    
    private func buildWaveLoadingSubView() {
        
        addSubview(waveLable)
        waveLable.snp.makeConstraints { (make) in
            make.center.equalToSuperview()
        }
        
        waveLable.numberOfLines = 0
        waveLable.textAlignment = .center
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
    }
    
    override func draw(_ rect: CGRect) {
        
        position =  (1 - progress) * Double(rect.height)
        
        //circle clip
        clipWithCircle()
        
        //draw wave
        drawWaveWater(originX - term / 5, fillColor: lightColor)
        drawWaveWater(originX, fillColor: heavyColor)
        
        //Let clipCircle above the waves
        clipWithCircle()
        
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        //计算周期calculate the term
        term =  Double(self.bounds.size.width) / cycle
        
        self.layer.cornerRadius = self.bounds.size.width * 0.5
        self.layer.masksToBounds = true
    }
    
    override func removeFromSuperview() {
        super.removeFromSuperview()
        waving = false
    }
    
    func clipWithCircle() {
        
        let circleRectWidth = min(self.bounds.size.width, self.bounds.size.height) - clipCircleLineWidth
        let circleRectOriginX = (self.bounds.size.width - circleRectWidth) / 2
        let circleRectOriginY = (self.bounds.size.height - circleRectWidth) / 2
        let circleRect = CGRect(x: circleRectOriginX, y: circleRectOriginY, width: circleRectWidth, height: circleRectWidth)
        
        var clipPath: UIBezierPath!
        if shapeModel == .circle {
            clipPath = UIBezierPath(ovalIn: circleRect)
        } else if shapeModel == .rect {
            clipPath = UIBezierPath(rect: circleRect)
        }
        
        clipCircleColor.setStroke()
        clipPath.lineWidth = clipCircleLineWidth
        clipPath.stroke()
        clipPath.addClip()
    }
    
    
    func drawWaveWater(_ originX: Double, fillColor: UIColor) {
        let curvePath = UIBezierPath()
        curvePath.move(to: CGPoint(x: originX, y: position))
        
        //循环,画波浪wave path
        var tempPoint = originX
        for _ in 1...rounding(4 * cycle) {//(2 * cycle)即可充满屏幕,即一个循环,为了移动画布使波浪移动,我们要画两个循环
            curvePath.addQuadCurve(to: keyPoint(tempPoint + term / 2, originX: originX), controlPoint: keyPoint(tempPoint + term / 4, originX: originX))
            tempPoint += term / 2
        }
        
        //close the water path
        curvePath.addLine(to: CGPoint(x: curvePath.currentPoint.x, y: self.bounds.size.height))
        curvePath.addLine(to: CGPoint(x: CGFloat(originX), y: self.bounds.size.height))
        curvePath.close()
        
        fillColor.setFill()
        curvePath.lineWidth = 10
        curvePath.fill()
        
    }
    
    func animationWave() {
        DispatchQueue.global().async {
            
            [weak self] () -> Void in
            
            if self != nil {
                let tempOriginX = self!.originX
                while self != nil && self!.waving {
                    if self!.originX <= tempOriginX - self!.term {
                        self!.originX = tempOriginX - self!.waveMoveSpan
                    } else {
                        self!.originX -= self!.waveMoveSpan
                    }
                    DispatchQueue.main.async(execute: { () -> Void in
                        self!.setNeedsDisplay()
                    })
                    Thread.sleep(forTimeInterval: self!.animationUnitTime)
                }
            }
        }
    }
    
    //determine the key point of curve
    func keyPoint(_ x: Double, originX: Double) -> CGPoint {
        //x为当前取点x坐标,columnYPoint的参数为相对于正弦函数原点的x坐标
        return CGPoint(x: x, y: columnYPoint(x - originX))
    }
    
    func columnYPoint(_ x: Double) -> Double {
        //三角正弦函数
        let result = amplitude * sin((2 * Double.pi / term) * x + phasePosition)
        return result + position
    }
    
    //四舍五入
    func rounding(_ value: Double) -> Int {
        let tempInt = Int(value)
        let tempDouble = Double(tempInt) + 0.5
        if value > tempDouble {
            return tempInt + 1
        } else {
            return tempInt
        }
    }

}