From 450d8d277ae5ac8afc819818ad1c22acc6219862 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 11 Mar 2025 13:14:04 +0100 Subject: [PATCH] Revert "UKCircularProgress" --- .../CircularProgressPreview.swift | 31 +-- .../Models/CircularProgressVM.swift | 74 ++--- .../CircularProgress/SUCircularProgress.swift | 67 +++-- .../CircularProgress/UKCircularProgress.swift | 254 ------------------ 4 files changed, 72 insertions(+), 354 deletions(-) delete mode 100644 Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift index 40c9edee..caff98de 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift @@ -6,36 +6,24 @@ struct CircularProgressPreview: View { @State private var model = Self.initialModel @State private var currentValue: CGFloat = Self.initialValue - private let circularProgress = UKCircularProgress( - model: Self.initialModel - ) - private let timer = Timer .publish(every: 0.5, on: .main, in: .common) .autoconnect() - + var body: some View { VStack { - PreviewWrapper(title: "UIKit") { - self.circularProgress - .preview - .onAppear { - self.circularProgress.currentValue = Self.initialValue - self.circularProgress.model = Self.initialModel - } - .onChange(of: model) { newModel in - self.circularProgress.model = newModel - } - .onChange(of: self.currentValue) { newValue in - self.circularProgress.currentValue = newValue - } - } PreviewWrapper(title: "SwiftUI") { SUCircularProgress(currentValue: self.currentValue, model: self.model) } Form { ComponentColorPicker(selection: self.$model.color) - CaptionFontPicker(selection: self.$model.font) + Picker("Font", selection: self.$model.font) { + Text("Default").tag(Optional.none) + Text("Small").tag(UniversalFont.smButton) + Text("Medium").tag(UniversalFont.mdButton) + Text("Large").tag(UniversalFont.lgButton) + Text("Custom: system bold of size 16").tag(UniversalFont.system(size: 16, weight: .bold)) + } Picker("Line Width", selection: self.$model.lineWidth) { Text("Default").tag(Optional.none) Text("2").tag(Optional.some(2)) @@ -68,9 +56,8 @@ struct CircularProgressPreview: View { private static var initialValue: Double { return 0.0 } - private static var initialModel = CircularProgressVM { - $0.label = "0%" + $0.label = "0" $0.style = .light } } diff --git a/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift b/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift index a032d056..194dd8ad 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift @@ -81,24 +81,24 @@ extension CircularProgressVM { return .lgCaption } } - var stripeWidth: CGFloat { - return 0.5 - } private func stripesCGPath(in rect: CGRect) -> CGMutablePath { + let stripeWidth: CGFloat = 0.5 let stripeSpacing: CGFloat = 3 let stripeAngle: Angle = .degrees(135) let path = CGMutablePath() let step = stripeWidth + stripeSpacing let radians = stripeAngle.radians - - let dx: CGFloat = rect.height * tan(radians) - for x in stride(from: 0, through: rect.width + rect.height, by: step) { + let dx = rect.height * tan(radians) + for x in stride(from: dx, through: rect.width + rect.height, by: step) { let topLeft = CGPoint(x: x, y: 0) - let bottomRight = CGPoint(x: x + dx, y: rect.height) - + let topRight = CGPoint(x: x + stripeWidth, y: 0) + let bottomLeft = CGPoint(x: x + dx, y: rect.height) + let bottomRight = CGPoint(x: x + stripeWidth + dx, y: rect.height) path.move(to: topLeft) + path.addLine(to: topRight) path.addLine(to: bottomRight) + path.addLine(to: bottomLeft) path.closeSubpath() } return path @@ -107,22 +107,30 @@ extension CircularProgressVM { extension CircularProgressVM { func gap(for normalized: CGFloat) -> CGFloat { - return normalized > 0 ? 0.05 : 0 + normalized > 0 ? 0.05 : 0 } - func stripedArcStart(for normalized: CGFloat) -> CGFloat { + func progressArcStart(for normalized: CGFloat) -> CGFloat { + return 0 + } + + func progressArcEnd(for normalized: CGFloat) -> CGFloat { + return max(0, min(1, normalized)) + } + + func backgroundArcStart(for normalized: CGFloat) -> CGFloat { let gapValue = self.gap(for: normalized) return max(0, min(1, normalized + gapValue)) } - func stripedArcEnd(for normalized: CGFloat) -> CGFloat { + func backgroundArcEnd(for normalized: CGFloat) -> CGFloat { let gapValue = self.gap(for: normalized) return 1 - gapValue } } extension CircularProgressVM { - func progress(for currentValue: CGFloat) -> CGFloat { + public func progress(for currentValue: CGFloat) -> CGFloat { let range = self.maxValue - self.minValue guard range > 0 else { return 0 } let normalized = (currentValue - self.minValue) / range @@ -130,48 +138,6 @@ extension CircularProgressVM { } } -// MARK: - UIKit Helpers - -extension CircularProgressVM { - var isStripesLayerHidden: Bool { - switch self.style { - case .light: - return true - case .striped: - return false - } - } - var isBackgroundLayerHidden: Bool { - switch self.style { - case .light: - return false - case .striped: - return true - } - } - func stripesBezierPath(in rect: CGRect) -> UIBezierPath { - let center = CGPoint(x: rect.midX, y: rect.midY) - let path = UIBezierPath(cgPath: self.stripesCGPath(in: rect)) - var transform = CGAffineTransform.identity - transform = transform - .translatedBy(x: center.x, y: center.y) - .rotated(by: -CGFloat.pi / 2) - .translatedBy(x: -center.x, y: -center.y) - path.apply(transform) - return path - } - func shouldInvalidateIntrinsicContentSize(_ oldModel: Self) -> Bool { - return self.preferredSize != oldModel.preferredSize - } - func shouldUpdateText(_ oldModel: Self) -> Bool { - return self.label != oldModel.label - } - func shouldRecalculateProgress(_ oldModel: Self) -> Bool { - return self.minValue != oldModel.minValue - || self.maxValue != oldModel.maxValue - } -} - // MARK: - SwiftUI Helpers extension CircularProgressVM { diff --git a/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift b/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift index 0ba970ac..96ebcc5d 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift @@ -102,33 +102,52 @@ public struct SUCircularProgress: View { } var stripedBackground: some View { - StripesShapeCircularProgress(model: self.model) - .stroke( - self.model.color.main.color, - style: StrokeStyle(lineWidth: self.model.stripeWidth) + Path { path in + path.addArc( + center: self.model.center, + radius: self.model.radius, + startAngle: .radians(0), + endAngle: .radians(2 * .pi), + clockwise: false ) - .mask { - Path { maskPath in - maskPath.addArc( - center: self.model.center, - radius: self.model.radius, - startAngle: .radians(0), - endAngle: .radians(2 * .pi), - clockwise: false + } + .trim( + from: self.model.backgroundArcStart(for: self.progress), + to: self.model.backgroundArcEnd(for: self.progress) + ) + .stroke( + .clear, + style: StrokeStyle( + lineWidth: self.model.circularLineWidth, + lineCap: .round + ) + ) + .overlay { + StripesShapeCircularProgress(model: self.model) + .foregroundColor(self.model.color.main.color) + .mask { + Path { maskPath in + maskPath.addArc( + center: self.model.center, + radius: self.model.radius, + startAngle: .radians(0), + endAngle: .radians(2 * .pi), + clockwise: false + ) + } + .trim( + from: self.model.backgroundArcStart(for: self.progress), + to: self.model.backgroundArcEnd(for: self.progress) ) - } - .trim( - from: self.model.stripedArcStart(for: self.progress), - to: self.model.stripedArcEnd(for: self.progress) - ) - .stroke( - style: StrokeStyle( - lineWidth: self.model.circularLineWidth, - lineCap: .round + .stroke( + style: StrokeStyle( + lineWidth: self.model.circularLineWidth, + lineCap: .round + ) ) - ) - } - .rotationEffect(.degrees(-90)) + } + } + .rotationEffect(.degrees(-90)) } } diff --git a/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift b/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift deleted file mode 100644 index e910e623..00000000 --- a/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift +++ /dev/null @@ -1,254 +0,0 @@ -import AutoLayout -import UIKit - -/// A UIKit component that displays a circular progress indicator. -open class UKCircularProgress: UIView, UKComponent { - // MARK: - Properties - - /// A model that defines the appearance properties for the circular progress. - public var model: CircularProgressVM { - didSet { - self.update(oldValue) - } - } - - /// The current progress value. - public var currentValue: CGFloat { - didSet { - self.updateProgress() - } - } - - // MARK: - Subviews - - /// The shape layer responsible for rendering the background of the circular progress indicator in a light style. - public let backgroundLayer = CAShapeLayer() - - /// The shape layer responsible for rendering the progress arc of the circular progress indicator. - public let progressLayer = CAShapeLayer() - - /// The shape layer responsible for rendering the striped effect in the circular progress indicator. - public let stripesLayer = CAShapeLayer() - - /// The shape layer that acts as a mask for `stripesLayer`, ensuring it has the intended shape. - public let stripesMaskLayer = CAShapeLayer() - - /// The label used to display text inside the circular progress indicator. - public let label = UILabel() - - // MARK: - UIView Properties - - open override var intrinsicContentSize: CGSize { - return self.model.preferredSize - } - - // MARK: - Initialization - - /// Initializer. - /// - Parameters: - /// - initialValue: The initial progress value. Defaults to `0`. - /// - model: The model that defines the appearance properties. - public init( - initialValue: CGFloat = 0, - model: CircularProgressVM = .init() - ) { - self.model = model - self.currentValue = initialValue - super.init(frame: .zero) - - self.setup() - self.style() - self.layout() - } - - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Setup - - private func setup() { - self.layer.addSublayer(self.backgroundLayer) - self.layer.addSublayer(self.stripesLayer) - self.layer.addSublayer(self.progressLayer) - self.addSubview(self.label) - - self.stripesLayer.mask = self.stripesMaskLayer - - if #available(iOS 17.0, *) { - self.registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: Self, _: UITraitCollection) in - view.handleTraitChanges() - } - } - - let progress = self.model.progress(for: self.currentValue) - self.progressLayer.strokeEnd = progress - if !self.model.isStripesLayerHidden { - self.stripesMaskLayer.strokeStart = self.model.stripedArcStart(for: progress) - self.stripesMaskLayer.strokeEnd = self.model.stripedArcEnd(for: progress) - } - self.label.text = self.model.label - } - - // MARK: - Style - - private func style() { - Self.Style.backgroundLayer(self.backgroundLayer, model: self.model) - Self.Style.progressLayer(self.progressLayer, model: self.model) - Self.Style.label(self.label, model: self.model) - Self.Style.stripesLayer(self.stripesLayer, model: self.model) - Self.Style.stripesMaskLayer(self.stripesMaskLayer, model: self.model) - } - - // MARK: - Update - - public func update(_ oldModel: CircularProgressVM) { - guard self.model != oldModel else { return } - self.style() - self.updateShapePaths() - - if self.model.shouldUpdateText(oldModel) { - UIView.transition( - with: self.label, - duration: self.model.animationDuration, - options: .transitionCrossDissolve, - animations: { - self.label.text = self.model.label - }, - completion: nil - ) - } - if self.model.shouldRecalculateProgress(oldModel) { - self.updateProgress() - } - if self.model.shouldInvalidateIntrinsicContentSize(oldModel) { - self.invalidateIntrinsicContentSize() - } - } - - private func updateShapePaths() { - let center = CGPoint(x: self.bounds.midX, y: self.bounds.midY) - let circlePath = UIBezierPath( - arcCenter: center, - radius: self.model.radius, - startAngle: -CGFloat.pi / 2, - endAngle: -CGFloat.pi / 2 + 2 * .pi, - clockwise: true - ) - - self.backgroundLayer.path = circlePath.cgPath - self.progressLayer.path = circlePath.cgPath - self.stripesMaskLayer.path = circlePath.cgPath - self.stripesLayer.path = self.model.stripesBezierPath(in: self.bounds).cgPath - } - - private func updateProgress() { - let progress = self.model.progress(for: self.currentValue) - - CATransaction.begin() - CATransaction.setAnimationDuration(self.model.animationDuration) - CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear)) - self.progressLayer.strokeEnd = progress - if !self.model.isStripesLayerHidden { - self.stripesMaskLayer.strokeStart = self.model.stripedArcStart(for: progress) - self.stripesMaskLayer.strokeEnd = self.model.stripedArcEnd(for: progress) - } - CATransaction.commit() - } - - // MARK: - Layout - - private func layout() { - self.label.center() - } - - open override func layoutSubviews() { - super.layoutSubviews() - - self.backgroundLayer.frame = self.bounds - self.progressLayer.frame = self.bounds - self.stripesLayer.frame = self.bounds - self.stripesMaskLayer.frame = self.bounds - - self.updateShapePaths() - } - - // MARK: - UIView Methods - - open override func sizeThatFits(_ size: CGSize) -> CGSize { - let preferred = self.model.preferredSize - return CGSize( - width: min(size.width, preferred.width), - height: min(size.height, preferred.height) - ) - } - - open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - self.handleTraitChanges() - } - - private func handleTraitChanges() { - Self.Style.backgroundLayer(self.backgroundLayer, model: self.model) - Self.Style.progressLayer(self.progressLayer, model: self.model) - Self.Style.stripesLayer(self.stripesLayer, model: self.model) - Self.Style.stripesMaskLayer(self.stripesMaskLayer, model: self.model) - } -} - -// MARK: - Style Helpers - -extension UKCircularProgress { - fileprivate enum Style { - static func backgroundLayer( - _ layer: CAShapeLayer, - model: CircularProgressVM - ) { - layer.fillColor = UIColor.clear.cgColor - layer.strokeColor = model.color.background.uiColor.cgColor - layer.lineCap = .round - layer.lineWidth = model.circularLineWidth - layer.isHidden = model.isBackgroundLayerHidden - } - - static func progressLayer( - _ layer: CAShapeLayer, - model: CircularProgressVM - ) { - layer.fillColor = UIColor.clear.cgColor - layer.strokeColor = model.color.main.uiColor.cgColor - layer.lineCap = .round - layer.lineWidth = model.circularLineWidth - } - - static func label( - _ label: UILabel, - model: CircularProgressVM - ) { - label.textAlignment = .center - label.adjustsFontSizeToFitWidth = true - label.minimumScaleFactor = 0.5 - label.font = model.titleFont.uiFont - label.textColor = model.color.main.uiColor - } - - static func stripesLayer( - _ layer: CAShapeLayer, - model: CircularProgressVM - ) { - layer.isHidden = model.isStripesLayerHidden - layer.strokeColor = model.color.main.uiColor.cgColor - layer.lineWidth = model.stripeWidth - } - - static func stripesMaskLayer( - _ layer: CAShapeLayer, - model: CircularProgressVM - ) { - layer.fillColor = UIColor.clear.cgColor - layer.strokeColor = model.color.background.uiColor.cgColor - layer.lineCap = .round - layer.lineWidth = model.circularLineWidth - } - } -}