diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 81a082b9..01b97406 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9258B2911C9DE00091998 /* EntryField.swift */; }; EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD062A62A3B67770015965D /* UIView+CALayer.swift */; }; EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */; }; + EAD0688E2A55F819002E3A2D /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD0688D2A55F819002E3A2D /* Loader.swift */; }; EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */; }; EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; }; EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; @@ -243,6 +244,7 @@ EAC9258B2911C9DE00091998 /* EntryField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = ""; }; EAD062A62A3B67770015965D /* UIView+CALayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+CALayer.swift"; sourceTree = ""; }; EAD062AF2A3B873E0015965D /* BadgeIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeIndicator.swift; sourceTree = ""; }; + EAD0688D2A55F819002E3A2D /* Loader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loader.swift; sourceTree = ""; }; EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIGestureRecognizer+Publisher.swift"; sourceTree = ""; }; EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = ""; }; EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = ""; }; @@ -407,6 +409,7 @@ EA985BF3296C609E00F2FF2E /* Icon */, EA3362412892EF700071C351 /* Label */, 44604AD529CE195300E62B51 /* Line */, + EAD0688C2A55F801002E3A2D /* Loader */, 445BA07629C07ABA0036A7C5 /* Notification */, EA89200B28B530F0006B9984 /* RadioBox */, EAF7F11428A1470D00B287F5 /* RadioButton */, @@ -703,6 +706,14 @@ path = BadgeIndicator; sourceTree = ""; }; + EAD0688C2A55F801002E3A2D /* Loader */ = { + isa = PBXGroup; + children = ( + EAD0688D2A55F819002E3A2D /* Loader.swift */, + ); + path = Loader; + sourceTree = ""; + }; EAF7F092289985E200B287F5 /* Checkbox */ = { isa = PBXGroup; children = ( @@ -918,6 +929,7 @@ EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */, EA985C7D297DAED300F2FF2E /* Primitive.swift in Sources */, EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */, + EAD0688E2A55F819002E3A2D /* Loader.swift in Sources */, EAB5FEF829393A7200998C17 /* ButtonGroupConstants.swift in Sources */, EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */, EA513A952A4E1F82002A4DFF /* TitleLockupStyleConfiguration.swift in Sources */, diff --git a/VDS/Classes/SelectorItemBase.swift b/VDS/Classes/SelectorItemBase.swift index b9291e7c..966b4d9c 100644 --- a/VDS/Classes/SelectorItemBase.swift +++ b/VDS/Classes/SelectorItemBase.swift @@ -11,7 +11,6 @@ import Combine import VDSColorTokens import VDSFormControlsTokens -/// Checkboxes are a multi-select component through which a customer indicates a choice. If a binary choice, the component is a checkbox. If the choice has multiple options, the component is a ``CheckboxGroup``. open class SelectorItemBase: Control, Errorable, Changeable { //-------------------------------------------------- diff --git a/VDS/Components/Loader/Loader.swift b/VDS/Components/Loader/Loader.swift new file mode 100644 index 00000000..607dd65a --- /dev/null +++ b/VDS/Components/Loader/Loader.swift @@ -0,0 +1,89 @@ +// +// 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. + public var isActive: Bool = true { didSet { setNeedsUpdate() } } + + /// The Int used to determine the height and width of the Loader + public var size: Int = 40 { didSet { setNeedsUpdate() } } + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + override open var layer: CAShapeLayer { + get { return super.layer as! CAShapeLayer } + } + + override open class var layerClass: AnyClass { + return CAShapeLayer.self + } + + open override func setup() { + super.setup() + addSubview(icon) + + NSLayoutConstraint.activate([ + icon.centerXAnchor.constraint(equalTo: centerXAnchor), + icon.centerYAnchor.constraint(equalTo: centerYAnchor), + icon.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), + icon.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor), + icon.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + icon.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor) + ]) + } + + 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") +} diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/loader.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/loader.imageset/Contents.json new file mode 100644 index 00000000..085b024c --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/loader.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "loader.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/loader.imageset/loader.svg b/VDS/SupportingFiles/Icons.xcassets/Restricted/loader.imageset/loader.svg new file mode 100644 index 00000000..21dd2443 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/loader.imageset/loader.svg @@ -0,0 +1,3 @@ + + + diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index b3737dfe..20b54f1c 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,7 @@ +1.0.27 +======= +- Added Loader View + 1.0.26 ======= - CXTDT-426626 - Toggle - Disabled "on" state