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