adding to src
This commit is contained in:
parent
f3cc8c479a
commit
15a51bd86f
181
MVMCoreUI/Atomic/Atoms/Views/LoadingSpinner.swift
Normal file
181
MVMCoreUI/Atomic/Atoms/Views/LoadingSpinner.swift
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
//
|
||||||
|
// LoadingSpinner.swift
|
||||||
|
// MVMCoreUI
|
||||||
|
//
|
||||||
|
// Created by Kevin Christiano on 5/20/20.
|
||||||
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
|
open class LoadingSpinner: View {
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Properties
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
public var strokeColor: UIColor = .mvmBlack
|
||||||
|
|
||||||
|
public var lineWidth: CGFloat = 3.0
|
||||||
|
|
||||||
|
public var speed: Float = 1.5
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Lifecycle
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
override open func setupView() {
|
||||||
|
super.setupView()
|
||||||
|
|
||||||
|
// TODO: remove
|
||||||
|
pinWidthAndHeight(radius: 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var layer: CAShapeLayer {
|
||||||
|
get { return super.layer as! CAShapeLayer }
|
||||||
|
}
|
||||||
|
|
||||||
|
override open class var layerClass: AnyClass {
|
||||||
|
return CAShapeLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
layer.fillColor = nil
|
||||||
|
layer.strokeColor = strokeColor.cgColor
|
||||||
|
layer.lineWidth = lineWidth
|
||||||
|
layer.lineCap = .butt
|
||||||
|
layer.speed = speed
|
||||||
|
layer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)).cgPath
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Animation
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
override open func didMoveToWindow() {
|
||||||
|
animate()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pose {
|
||||||
|
let secondsSincePriorPose: CFTimeInterval
|
||||||
|
let start: CGFloat
|
||||||
|
let length: CGFloat
|
||||||
|
init(_ secondsSincePriorPose: CFTimeInterval, _ start: CGFloat, _ length: CGFloat) {
|
||||||
|
self.secondsSincePriorPose = secondsSincePriorPose
|
||||||
|
self.start = start
|
||||||
|
self.length = length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This needs more attention
|
||||||
|
class var poses: [Pose] {
|
||||||
|
get {
|
||||||
|
return [
|
||||||
|
Pose(0.0, 0.000, 0.8),
|
||||||
|
Pose(0.6, 0.500, 0.5),
|
||||||
|
Pose(0.6, 1.000, 0.3),
|
||||||
|
Pose(0.6, 1.500, 0.1),
|
||||||
|
Pose(0.2, 1.875, 0.1),
|
||||||
|
Pose(0.2, 2.250, 0.3),
|
||||||
|
Pose(0.2, 2.625, 0.5),
|
||||||
|
Pose(0.2, 3.000, 0.7)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animate() {
|
||||||
|
var time: CFTimeInterval = 0
|
||||||
|
var times = [CFTimeInterval]()
|
||||||
|
var start: CGFloat = 0
|
||||||
|
var rotations = [CGFloat]()
|
||||||
|
var strokeEnds = [CGFloat]()
|
||||||
|
|
||||||
|
let poses = Self.poses
|
||||||
|
let totalSeconds = poses.reduce(0) { $0 + $1.secondsSincePriorPose }
|
||||||
|
|
||||||
|
for pose in poses {
|
||||||
|
time += pose.secondsSincePriorPose
|
||||||
|
times.append(time / totalSeconds)
|
||||||
|
start = pose.start
|
||||||
|
rotations.append(start * 2 * CGFloat.pi)
|
||||||
|
strokeEnds.append(pose.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
times.append(times.last!)
|
||||||
|
rotations.append(rotations[0])
|
||||||
|
strokeEnds.append(strokeEnds[0])
|
||||||
|
|
||||||
|
animateKeyPath(keyPath: "strokeEnd", duration: totalSeconds, times: times, values: strokeEnds)
|
||||||
|
animateKeyPath(keyPath: "transform.rotation", duration: totalSeconds, times: times, values: rotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animateKeyPath(keyPath: String, duration: CFTimeInterval, times: [CFTimeInterval], values: [CGFloat]) {
|
||||||
|
|
||||||
|
let animation = CAKeyframeAnimation(keyPath: keyPath)
|
||||||
|
animation.keyTimes = times as [NSNumber]?
|
||||||
|
animation.values = values
|
||||||
|
animation.calculationMode = .linear
|
||||||
|
animation.duration = duration
|
||||||
|
animation.rotationMode = .rotateAuto
|
||||||
|
animation.isRemovedOnCompletion = false
|
||||||
|
animation.repeatCount = .infinity
|
||||||
|
layer.add(animation, forKey: animation.keyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Methods
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
func resumeSpinnerAfterDelay() {
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
|
self?.resumeAnimations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pauseAnimations() {
|
||||||
|
let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
|
||||||
|
layer.speed = 0
|
||||||
|
isHidden = true
|
||||||
|
layer.timeOffset = pausedTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func resumeAnimations() {
|
||||||
|
let pausedTime = layer.timeOffset
|
||||||
|
isHidden = false
|
||||||
|
layer.speed = speed
|
||||||
|
layer.timeOffset = 0
|
||||||
|
layer.beginTime = 0
|
||||||
|
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
|
||||||
|
layer.beginTime = timeSincePause
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopAllAnimations() {
|
||||||
|
layer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pinWidthAndHeight(radius: CGFloat) {
|
||||||
|
let diameter: CGFloat = radius * 2 + lineWidth
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
heightAnchor.constraint(equalToConstant: diameter),
|
||||||
|
widthAnchor.constraint(equalToConstant: diameter)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - MoleculeViewProtocol
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
|
super.set(with: model, delegateObject, additionalData)
|
||||||
|
guard let model = model as? LoadingSpinnerModel else { return }
|
||||||
|
|
||||||
|
strokeColor = model.strokeColor.uiColor
|
||||||
|
lineWidth = model.lineWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
|
||||||
|
return 40.0
|
||||||
|
}
|
||||||
|
}
|
||||||
58
MVMCoreUI/Atomic/Atoms/Views/LoadingSpinnerModel.swift
Normal file
58
MVMCoreUI/Atomic/Atoms/Views/LoadingSpinnerModel.swift
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// LoadingSpinnerModel.swift
|
||||||
|
// MVMCoreUI
|
||||||
|
//
|
||||||
|
// Created by Kevin Christiano on 5/20/20.
|
||||||
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
open class LoadingSpinnerModel: MoleculeModelProtocol {
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Properties
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
public var backgroundColor: Color?
|
||||||
|
public static var identifier: String = "loadingSpinner"
|
||||||
|
public var strokeColor = Color(uiColor: .mvmBlack)
|
||||||
|
public var lineWidth: CGFloat = 3
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Keys
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case moleculeName
|
||||||
|
case backgroundColor
|
||||||
|
case strokeColor
|
||||||
|
case lineWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Codec
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
required public init(from decoder: Decoder) throws {
|
||||||
|
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
||||||
|
|
||||||
|
if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) {
|
||||||
|
self.strokeColor = strokeColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if let lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) {
|
||||||
|
self.lineWidth = lineWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
|
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||||
|
try container.encode(strokeColor, forKey: .strokeColor)
|
||||||
|
try container.encode(lineWidth, forKey: .lineWidth)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user