ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ios - Swift] Phi Chart View (CALayer)
    ios 2021. 1. 12. 10:18

     ()

     

    이번 글에서는 지난번에 배운 UIBezierPath, CAShapeLayer를 사용해 파이 차트를 만들어 보도록 하겠습니다.

     

     

     

    import UIKit
    class UIPieChatView: UIView {
    
        override init(frame: CGRect) {
            super.init(frame: frame)
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
        }
        
        override func setNeedsDisplay() {
            super.setNeedsDisplay()
            definePieChatLayer()
        }
        
        private func definePieChatLayer() {
        
        }
    }

     

    UIView를 상속받은 기본적인 클래스 구성입니다. 

     

    앞으로 이 클래스에 사용자가 원하는 값을 넣게 되면 우리는 setNeedsDisplayer()를 호출해 뷰를 다시 그려줄 수 있도록 하면 됩니다.

     

     

     

    class UIPieChatView: UIView {
        private let pieBezierPath = UIBezierPath()
        
        var colors: [CGColor]? {
            didSet {
                setNeedsDisplay()
            }
        }
        var values: [CGFloat]? {
            didSet {
                setNeedsDisplay()
            }
        }
        
    중략 .... 

     

    파이 차트의 원하는 컬러와 밸류 값을 배열 형태로 추가해 줄 수 있도록 합니다.

     

    파이 차트를 채워줄 베지어 패스도 마찬가지로 추가하고 이제 setNeedsDisplayer()를 호출 시 definePieChatLayer()를 호출해서 레이어를 추가해주면 됩니다.

     

     

    private func definePieChatLayer() {
    
        guard let values = values else { return }
        guard let colors = colors else { return }
        
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        
        var sAngle: CGFloat = 0
        var eAngle: CGFloat = 0
        
        for i in 0..<values.count {
            sAngle = eAngle
            eAngle = sAngle + values[i]
            
            pieBezierPath.removeAllPoints()
            pieBezierPath.move(to: center)
            pieBezierPath.addArc(withCenter: center, radius: 150, startAngle: sAngle * 2 * CGFloat.pi, endAngle: eAngle * 2 * CGFloat.pi, clockwise: true)
            pieBezierPath.close()
            
            let pieLayer = CAShapeLayer()
            pieLayer.path = pieBezierPath.cgPath
            pieLayer.lineWidth = 5
            pieLayer.strokeEnd = 1
            pieLayer.fillColor = colors[i]
            pieLayer.strokeColor = UIColor.white.cgColor
            
            layer.addSublayer(pieLayer)
        }
        
    중략 ....

    베지어 패스에 values 값을 계산해 호를 그리고 컬러의 값을 채울 수 있도록 하였습니다.

    strokeColor를 화이트로 변경해 구분선을 표시할 수 있도록 하였습니다. 

     

    definePieChatLayer()를 사용하려면 Values, colors를 설정해 줘야겠죠?

     

    ViewContoller에 가서 UIView를 추가하고 Class를 UIPieChatView 변경한 후 아래 코드를 추가해 줍니다.

     

    @IBOutlet weak var piChat: UIPieChatView!
    
    override func viewDidLoad() {
        piChat.colors = [UIColor.red.cgColor, UIColor.orange.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor]
        piChat.values = [0.25, 0.45, 0.10, 0.20]
        
    }

    각 순서에 맞는 %로 합이 1의 값을 가질 수 있도록 했습니다.

     

    중간 결과를 보면 아래와 같이 나타나는 것을 볼 수 있습니다.

     

     

     

     

    이제 텍스트 CATextLayer를 이용하여 각 부분에 해당하는 비율이 얼마인지 보여주도록 합니다.

     

    private func definePieChatLayer() {
    
    ... 중략
    
    
        for i in 0..<values.count {
            sAngle = eAngle
            eAngle = sAngle + values[i]
            mAngle = sAngle + ((eAngle - sAngle) / 2) // ---- 추가
            
            ... 중략 
            
            let y:CGFloat = sin((mAngle * 2 * CGFloat.pi)) * 100 // ---- 추가
            let x:CGFloat = cos((mAngle * 2 * CGFloat.pi)) * 100 // ---- 추가
            
            let pieFontLayer = CATextLayer()
            pieFontLayer.frame = CGRect(x: center.x + x, y: center.y + y, width: 0, height: 0).insetBy(dx: -50, dy: -7.5)
            pieFontLayer.foregroundColor = UIColor.white.cgColor
            pieFontLayer.string = "\(values[i] * 100)%"
            pieFontLayer.alignmentMode = .center
            pieFontLayer.fontSize = 15
            pieFontLayer.font = "System" as CFTypeRef
            pieFontLayer.isWrapped = true 
            
            layer.addSublayer(pieLayer)
            layer.addSublayer(pieFontLayer) // ---- 추가
        }
        
    }

     

    mAngle이라는 변수는 각 호의 중간 부분 라디언값을 계산해 텍스트를 추가해 줄 좌표를 구한 것입니다.

     

    구한 좌표값에서 insetBy를 사용해 텍스트 프레임을 구하도록 했습니다.

     

     

    각 영역에 해당하는 부분에 텍스트가 자리한 걸 확인할 수 있습니다.

     

    이제 애니메이션을 사용해 파이가 그려지는 것처럼 보일 수 있도록 변경하겠습니다.

     

     

    class UIPieChatView: UIView {
    
        private let maskLayer = CAShapeLayer()
        private let maskBezierPath = UIBezierPath()
        
        ... 중략
        
        private func definePieChatLayer() {
        
            ...
            
            maskBezierPath.removeAllPoints()
            maskBezierPath.addArc(withCenter: center, radius: 75, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
            
            maskLayer.path = maskBezierPath.cgPath
            maskLayer.lineWidth = 150
            maskLayer.strokeEnd = 0
            maskLayer.fillColor = UIColor.clear.cgColor
            maskLayer.strokeColor = UIColor.black.cgColor
        
        	layer.mask = maskLayer
        }

    마스크로 사용할 레이어와 베지어 패스를 선언하고 호의 반지름을 75로 설정합니다. 

     

    파이의 반지름이 150이어서 마스킹할 호를 75로 설정 후 라인 굵기를 150으로 선언해서 굵은 선이 파이를 채운다고 보면 됩니다. 

     

    func phiAnimate(duration: CFTimeInterval) {
        maskLayer.strokeEnd = 0
        maskLayer.removeAnimation(forKey: "maskAnimation")
        
        pieAnimation.keyPath = "strokeEnd"
        pieAnimation.duration = duration
        pieAnimation.toValue = 1
        pieAnimation.fillMode = .forwards
        pieAnimation.isRemovedOnCompletion = false
        maskLayer.add(pieAnimation, forKey: "maskAnimation")
    }

    뷰 컨트롤러에서 phiAnimate()를 호출해주도록 합니다.

     

     

    댓글

Designed by Tistory.