adding to src

This commit is contained in:
Kevin G Christiano 2020-05-20 16:36:41 -04:00
parent f3cc8c479a
commit 15a51bd86f
2 changed files with 239 additions and 0 deletions

View 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
}
}

View 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)
}
}