ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ios - Swift] Circle Progressbar 만들기 (CALayer)
    ios 2021. 1. 8. 13:53

     

    이번에는 위 이미지와 같은 CAGradientLayer, CAShapeLayer를 사용한 원형 프로그래스 바를 만들어 보도록 하겠습니다.

     

    Layer에 대한 자료는 baked-corn.tistory.com/110 참조해 보는 것도 좋고 간단하게 모든 뷰는 UI를 담당하는 하나의 CALayer를 가지고 있고  CALayer는 여러 subLayer를 가질 수 있다고 보면 될 것 같습니다.

     

     

    우선 이미지와 같이 프로그래스 바가 진행하면서 보일 부분을 나타내기 위해 CAGradientLayer를 사용해 뷰에 대한 레이어를 설정해 주면 됩니다.

     

    import UIKit
    
    class UICircleProgressbar: UIView {
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    }

    UIView를 상속받은 객체를 생성 후 아래의 함수들을 추가해주도록 합니다.

     

    func setup() {
        layer.addSublayer(gradientLayer)
    }

     

    private var _layerCGColors: [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor]
    
    private func defineProgressLayer() {
        gradientLayer.frame = bounds
        gradientLayer.type = .conic
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 0.0, y: 0)
        gradientLayer.colors = _layerCGColors
    }
    override func setNeedsDisplay() {
        super.setNeedsDisplay()
        defineProgressLayer()
    }

     

    setNeedsDisplay를 통해 뷰를 다시 그려야 할 때 CAGradientLayer의 영역과 컬러에 대해 설정하는 함수를 호출합니다.

     

    type을. conic으로 설정해 원뿔 형태의 Gradient를 그리도록 하겠습니다.

     

    startPoint, endPoint의 설정은 아래의 UIView의 좌표점에서 시작점과 끝점을 나타냅니다.

     

     

    중간 결과 확인 시 아래와 같은 뷰를 볼 수 있습니다.

     

     

     

     

    이제 링을 만들어 줘야 하는데 UIBezierPath를 사용해 경로를 만든 후 Mask에 추가해줍니다.

     

    private let progressLayer = CAShapeLayer() // ---- 추가
    private let progressPath = UIBezierPath() // ---- 추가
    
    func setup() {
        // ----- 추가
        layer.mask = progressLayer
    }
    private func defineProgressLayer() {
        // ----- 추가
        progressPath.removeAllPoints()
        progressPath.addArc(withCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: _radius, startAngle: _startAngle, endAngle: _endAngle, clockwise: true)
     
     
        progressLayer.path = progressPath.cgPath
        progressLayer.lineCap = .round
        progressLayer.lineWidth = 10.0
        progressLayer.strokeEnd = 1
        progressLayer.strokeColor = UIColor.green.cgColor
    }

     

     

    lineCap : 선 끝의 모양을 나타내는 방법으로 둥글게 처리하도록 했습니다.

    strokeEnd : 지정해준 Path를 어디까지 그릴 것인지 설정해주는 것으로 이해하면 됩니다. 처음부터 끝까지 그린다고 생각하면 됩니다.

     

    주의할 점은 stroke color를 지정해주지 않으면 lineWidth를 지정하더라도 mask 되지 않습니다.

    color는 clear 이외의 색상이여만 masking 처리됩니다.

     

    앱 실행 시 링이 아닌 원 모양이 나오는 것을 확인할 수 있는데 이것은 채우는 색상을 지정해주지 않아 그렇습니다. 

     

    아래 소스를 추가해 링으로 만듭니다. 

    progressLayer.fillColor = UIColor.clear.cgColor

     

     

     

    좌 : Fill Color 미지정 / 우 : Fill Color Clear 지정

     

     

     

    링까지 만들었으니 이제 Animation을 사용해 진행 상태를 표시하도록 합니다.

     

    private func defineProgressLayer() {
        progressLayer.strokeEnd = 0 // ---- 수정
    }

     

    private let progressAnimation = CABasicAnimation()
    private var _preProgressValue : CGFloat = 0
    private var _progressValue: CGFloat = 0
    
    var progressValue: CGFloat {
        get {
            return _progressValue
        }
        set {
            if _progressValue != newValue {
                _preProgressValue = _progressValue
                _progressValue = newValue < 0 ? 0 : newValue > 1 ? 1 : newValue
                progressAnimation(oldValue: _preProgressValue, newValue: _progressValue)
            }
        }
    }

     

    private func progressAnimation(oldValue: CGFloat, newValue: CGFloat) {
        progressLayer.strokeEnd = oldValue
        progressLayer.removeAnimation(forKey: "progressAnimation")
         
        progressAnimation.keyPath = "strokeEnd"
        progressAnimation.duration = 0.25
        progressAnimation.toValue = newValue
        progressAnimation.fillMode = .forwards
        progressAnimation.isRemovedOnCompletion = false
        progressLayer.add(progressAnimation, forKey: "progressAnimation")
    }

     

    progressValue의 값을 0 ~ 1 사이의 값으로 설정해 아래와 같이 움직일 수 있습니다.

     

     

    댓글

Designed by Tistory.