// // Loader.swift // VDS // // Created by Matt Bruce on 7/5/23. // import Foundation import UIKit import VDSColorTokens @objc(VDSLoader) /// A loader is an indicator that uses animation to show customers that there is an indefinite amount of wait time while a task is ongoing, e.g. a page is loading, a form is being submitted. The component disappears when the task is complete. open class Loader: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var icon = Icon().with { $0.name = .loader } private var opacity: CGFloat = 0.8 private var iconColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// Loader will be active if 'active' prop is passed. open var isActive: Bool = true { didSet { setNeedsUpdate() } } /// The Int used to determine the height and width of the Loader open var size: Int = 40 { didSet { setNeedsUpdate(); invalidateIntrinsicContentSize() } } /// The natural size for the receiving view, considering only properties of the view itself. open override var intrinsicContentSize: CGSize { .init(width: size, height: size) } //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() addSubview(icon) icon .pinTopGreaterThanOrEqualTo() .pinLeadingGreaterThanOrEqualTo() .pinTrailingLessThanOrEqualTo() .pinBottomLessThanOrEqualTo() NSLayoutConstraint.activate([ icon.centerXAnchor.constraint(equalTo: centerXAnchor), icon.centerYAnchor.constraint(equalTo: centerYAnchor) ]) } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() icon.color = iconColorConfiguration.getColor(self) icon.customSize = size if isActive { startAnimating() } else { stopAnimating() } } //-------------------------------------------------- // MARK: - Animation //-------------------------------------------------- private let rotationLayerName = "rotationAnimation" func startAnimating() { icon.layer.remove(layerName: rotationLayerName) let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.toValue = NSNumber(value: Double.pi * 2) rotation.duration = 0.5 // the speed of the rotation rotation.isCumulative = true rotation.repeatCount = Float.greatestFiniteMagnitude icon.layer.add(rotation, forKey: rotationLayerName) } func stopAnimating() { icon.layer.removeAnimation(forKey: rotationLayerName) } } extension Icon.Name { static let loader = Icon.Name(name: "loader") }