LNCircleProgressView.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. //
  2. // LNCircleProgressView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/12.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNCircleProgressView: UIView {
  11. var lineWidth: CGFloat = 3.0
  12. var trackColor: UIColor = UIColor.lightGray.withAlphaComponent(0.5)
  13. var progressColor: UIColor = .fill_5
  14. private(set) var progress: CGFloat = 0.0 {
  15. didSet {
  16. guard progress >= 0, progress <= 1 else {
  17. self.progress = max(0, min(1, progress))
  18. return
  19. }
  20. setNeedsDisplay()
  21. }
  22. }
  23. private var animationDuration: TimeInterval = 0.6
  24. private let progressLayer = CAShapeLayer()
  25. private let trackLayer = CAShapeLayer()
  26. override init(frame: CGRect) {
  27. super.init(frame: frame)
  28. setupViews()
  29. }
  30. func setProgress(_ progress: CGFloat, animated: Bool = true) {
  31. let targetProgress = max(0, min(1, progress))
  32. progressLayer.removeAllAnimations()
  33. if animated {
  34. // 创建进度动画
  35. let animation = CABasicAnimation(keyPath: "strokeEnd")
  36. animation.fromValue = self.progressLayer.strokeEnd
  37. animation.toValue = targetProgress
  38. animation.duration = animationDuration
  39. animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
  40. progressLayer.add(animation, forKey: "progressAnimation")
  41. progressLayer.strokeEnd = targetProgress
  42. } else {
  43. progressLayer.strokeEnd = targetProgress
  44. }
  45. self.progress = targetProgress
  46. }
  47. required init?(coder: NSCoder) {
  48. fatalError("init(coder:) has not been implemented")
  49. }
  50. override func layoutSubviews() {
  51. super.layoutSubviews()
  52. updateLayerFrames()
  53. }
  54. }
  55. extension LNCircleProgressView {
  56. private func setupViews() {
  57. trackLayer.fillColor = nil
  58. trackLayer.strokeColor = trackColor.cgColor
  59. trackLayer.lineWidth = lineWidth
  60. trackLayer.lineCap = .round
  61. layer.addSublayer(trackLayer)
  62. progressLayer.fillColor = nil
  63. progressLayer.strokeColor = progressColor.cgColor
  64. progressLayer.lineWidth = lineWidth
  65. progressLayer.lineCap = .round
  66. progressLayer.strokeEnd = 0
  67. layer.addSublayer(progressLayer)
  68. }
  69. private func updateLayerFrames() {
  70. let center = CGPoint(x: bounds.midX, y: bounds.midY)
  71. let radius = min(bounds.width, bounds.height) / 2 - lineWidth / 2
  72. // 创建圆形路径(从12点钟方向开始,顺时针绘制)
  73. let path = UIBezierPath(
  74. arcCenter: center,
  75. radius: radius,
  76. startAngle: -CGFloat.pi / 2,
  77. endAngle: 3 * CGFloat.pi / 2,
  78. clockwise: true
  79. )
  80. trackLayer.path = path.cgPath
  81. trackLayer.frame = bounds
  82. progressLayer.path = path.cgPath
  83. progressLayer.frame = bounds
  84. }
  85. }
  86. #if DEBUG
  87. import SwiftUI
  88. struct LNCircleProgressViewPreview: UIViewRepresentable {
  89. func makeUIView(context: Context) -> some UIView {
  90. let container = UIView()
  91. container.backgroundColor = .black.withAlphaComponent(0.5)
  92. let view = LNCircleProgressView()
  93. container.addSubview(view)
  94. view.snp.makeConstraints { make in
  95. make.center.equalToSuperview()
  96. make.width.height.equalTo(30)
  97. }
  98. view.setProgress(0.5)
  99. return container
  100. }
  101. func updateUIView(_ uiView: UIViewType, context: Context) { }
  102. }
  103. #Preview(body: {
  104. LNCircleProgressViewPreview()
  105. })
  106. #endif