| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- //
- // LNCircleProgressView.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/12.
- //
- import Foundation
- import UIKit
- import SnapKit
- class LNCircleProgressView: UIView {
- var lineWidth: CGFloat = 3.0
- var trackColor: UIColor = UIColor.lightGray.withAlphaComponent(0.5)
- var progressColor: UIColor = .fill_5
- private(set) var progress: CGFloat = 0.0 {
- didSet {
- guard progress >= 0, progress <= 1 else {
- self.progress = max(0, min(1, progress))
- return
- }
- setNeedsDisplay()
- }
- }
-
- private var animationDuration: TimeInterval = 0.6
-
- private let progressLayer = CAShapeLayer()
- private let trackLayer = CAShapeLayer()
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- setupViews()
- }
-
- func setProgress(_ progress: CGFloat, animated: Bool = true) {
- let targetProgress = max(0, min(1, progress))
-
- progressLayer.removeAllAnimations()
- if animated {
- // 创建进度动画
- let animation = CABasicAnimation(keyPath: "strokeEnd")
- animation.fromValue = self.progressLayer.strokeEnd
- animation.toValue = targetProgress
- animation.duration = animationDuration
- animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
-
- progressLayer.add(animation, forKey: "progressAnimation")
- progressLayer.strokeEnd = targetProgress
- } else {
- progressLayer.strokeEnd = targetProgress
- }
-
- self.progress = targetProgress
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func layoutSubviews() {
- super.layoutSubviews()
- updateLayerFrames()
- }
- }
- extension LNCircleProgressView {
- private func setupViews() {
- trackLayer.fillColor = nil
- trackLayer.strokeColor = trackColor.cgColor
- trackLayer.lineWidth = lineWidth
- trackLayer.lineCap = .round
- layer.addSublayer(trackLayer)
-
-
- progressLayer.fillColor = nil
- progressLayer.strokeColor = progressColor.cgColor
- progressLayer.lineWidth = lineWidth
- progressLayer.lineCap = .round
- progressLayer.strokeEnd = 0
- layer.addSublayer(progressLayer)
- }
-
- private func updateLayerFrames() {
- let center = CGPoint(x: bounds.midX, y: bounds.midY)
- let radius = min(bounds.width, bounds.height) / 2 - lineWidth / 2
-
- // 创建圆形路径(从12点钟方向开始,顺时针绘制)
- let path = UIBezierPath(
- arcCenter: center,
- radius: radius,
- startAngle: -CGFloat.pi / 2,
- endAngle: 3 * CGFloat.pi / 2,
- clockwise: true
- )
-
- trackLayer.path = path.cgPath
- trackLayer.frame = bounds
-
- progressLayer.path = path.cgPath
- progressLayer.frame = bounds
- }
- }
- #if DEBUG
- import SwiftUI
- struct LNCircleProgressViewPreview: UIViewRepresentable {
- func makeUIView(context: Context) -> some UIView {
- let container = UIView()
- container.backgroundColor = .black.withAlphaComponent(0.5)
-
- let view = LNCircleProgressView()
- container.addSubview(view)
- view.snp.makeConstraints { make in
- make.center.equalToSuperview()
- make.width.height.equalTo(30)
- }
-
- view.setProgress(0.5)
-
- return container
- }
-
- func updateUIView(_ uiView: UIViewType, context: Context) { }
- }
- #Preview(body: {
- LNCircleProgressViewPreview()
- })
- #endif
|