Jiwift

[iOS/Swift] UIKit 둥근 Progressbar Arc Progress Bar, Semi-circular Progress Bar 본문

iOS Dev/UIKit

[iOS/Swift] UIKit 둥근 Progressbar Arc Progress Bar, Semi-circular Progress Bar

지위프트 2025. 2. 21. 20:25
반응형

그래프 예시

둥근 모양의 Circle Progress입니다. 진행 상태를 직관적으로 보여주는 반원형 게이지 스타일의 커스텀 프로그레스바입니다.

주요 기능:

  • 프로그레스바 색상 커스터마이징 가능
  • 선 두께 조절 가능 (progressWidth)
  • 시작/끝 각도 설정을 통한 호(arc) 범위 조절
  • 기본 설정: 150도에서 시작하여 390도까지 시계방향으로 진행
  • 미완료 부분은 음영 효과로 입체감 표현
  • SnapKit을 활용한 오토레이아웃 지원

 

사용예시

// 기본 설정으로 생성
let progressView = CircleProgressView(progressColor: .systemBlue)

// 커스텀 설정
let customProgress = CircleProgressView(
    startAngle: 180,    // 시작 각도
    endAngle: 360,      // 종료 각도
    progressColor: .systemGreen,  // 진행 색상
    progressWidth: 15.0  // 선 두께
)

// 진행률 설정 (0.0 ~ 1.0)
progressView.progress = 0.7  // 70% 진행

 

Circle ProgressView 코드

class CircleProgressView: UIView {
    // MARK: - Properties
    private let progressLayer = CAShapeLayer()
    private let trackLayer = CAShapeLayer()
    
    private let startAngle: CGFloat
    private let endAngle: CGFloat
    private let progressColor: UIColor
    private let progressWidth: CGFloat
    
    var progress: CGFloat = 0 {
        didSet {
            updateProgress()
        }
    }
    
    // MARK: - Initialization
    init(frame: CGRect = .zero,
         startAngle: CGFloat = 150,
         endAngle: CGFloat = 390,
         progressColor: UIColor,
         progressWidth: CGFloat = 10.0) {
        self.startAngle = startAngle * CGFloat.pi / 180
        self.endAngle = endAngle * CGFloat.pi / 180
        self.progressColor = progressColor
        self.progressWidth = progressWidth
        super.init(frame: frame)
        setupLayers()
    }
    
    required init?(coder: NSCoder) {
        self.startAngle = 150 * CGFloat.pi / 180
        self.endAngle = 390 * CGFloat.pi / 180
        self.progressColor = .systemBlue
        self.progressWidth = 10.0
        super.init(coder: coder)
        setupLayers()
    }
    
    // MARK: - Setup
    private func setupLayers() {
        // 배경 트랙 레이어 설정
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.strokeColor = UIColor.systemGray4.cgColor
        trackLayer.lineCap = .round
        trackLayer.lineWidth = 10.0
        // 내부 그림자 효과 설정
        trackLayer.shadowColor = UIColor.black.cgColor
        trackLayer.shadowOffset = CGSize(width: 1, height: 1)
        trackLayer.shadowOpacity = 0.3
        trackLayer.shadowRadius = 2
        
        // 내부에 파인 듯한 효과를 위한 추가 레이어
        let innerShadow = CALayer()
        innerShadow.frame = bounds
        innerShadow.shadowColor = UIColor.white.cgColor
        innerShadow.shadowOffset = CGSize(width: -1, height: -1)
        innerShadow.shadowOpacity = 0.5
        innerShadow.shadowRadius = 2
        trackLayer.addSublayer(innerShadow)
        layer.addSublayer(trackLayer)
        
        // 프로그레스 레이어 설정
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.strokeColor = progressColor.cgColor
        progressLayer.lineCap = .round
        progressLayer.lineWidth = 10.0
        layer.addSublayer(progressLayer)
    }
    
    // MARK: - Layout
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = min(bounds.width, bounds.height) / 2 - progressLayer.lineWidth / 2
        
        // 저장된 시작 각도와 끝 각도 사용
        
        let circlePath = UIBezierPath(arcCenter: center,
                                    radius: radius,
                                    startAngle: startAngle,
                                    endAngle: endAngle,
                                    clockwise: true)
        
        trackLayer.path = circlePath.cgPath
        progressLayer.path = circlePath.cgPath
    }
    
    // MARK: - Progress Update
    private func updateProgress() {
        // progress 값을 0~1 사이로 제한
        let clampedProgress = min(max(progress, 0), 1)
        progressLayer.strokeEnd = clampedProgress
    }
}
반응형