Last active
February 24, 2020 14:33
-
-
Save jonahaung/08de81b274ac6b4ba62c3265c3e53944 to your computer and use it in GitHub Desktop.
SwiftUI Activity Indicator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SpinningView.swift | |
// BalarSarYwat | |
// | |
// Created by Aung Ko Min on 10/2/20. | |
// Copyright © 2020 Aung Ko Min. All rights reserved. | |
// | |
import UIKit | |
class SpinnerView : UIView { | |
override var layer: CAShapeLayer { | |
get { | |
return super.layer as! CAShapeLayer | |
} | |
} | |
override class var layerClass: AnyClass { | |
return CAShapeLayer.self | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
layer.fillColor = nil | |
layer.strokeColor = UIColor.black.cgColor | |
layer.lineWidth = 3 | |
setPath() | |
} | |
override func didMoveToWindow() { | |
animate() | |
} | |
private func setPath() { | |
layer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: layer.lineWidth / 2, dy: layer.lineWidth / 2)).cgPath | |
} | |
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 | |
} | |
} | |
class var poses: [Pose] { | |
get { | |
return [ | |
Pose(0.0, 0.000, 0.7), | |
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), | |
] | |
} | |
} | |
func animate() { | |
var time: CFTimeInterval = 0 | |
var times = [CFTimeInterval]() | |
var start: CGFloat = 0 | |
var rotations = [CGFloat]() | |
var strokeEnds = [CGFloat]() | |
let poses = type(of: 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 * .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) | |
animateStrokeHueWithDuration(duration: totalSeconds * 5) | |
} | |
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.repeatCount = Float.infinity | |
layer.add(animation, forKey: animation.keyPath) | |
} | |
func animateStrokeHueWithDuration(duration: CFTimeInterval) { | |
let count = 36 | |
let animation = CAKeyframeAnimation(keyPath: "strokeColor") | |
animation.keyTimes = (0 ... count).map { NSNumber(value: CFTimeInterval($0) / CFTimeInterval(count)) } | |
animation.values = (0 ... count).map { | |
UIColor(hue: CGFloat($0) / CGFloat(count), saturation: 1, brightness: 1, alpha: 1).cgColor | |
} | |
animation.duration = duration | |
animation.calculationMode = .linear | |
animation.repeatCount = Float.infinity | |
layer.add(animation, forKey: animation.keyPath) | |
} | |
} | |
private let spinningView = SpinnerView(frame: CGRect(origin: .zero, size: CGSize(width: 50, height: 50))) | |
func setLoading(_ isLoading: Bool) { | |
guard let window = SceneDelegate.sharedInstance?.window else { return } | |
if isLoading && !window.subviews.contains(spinningView) { | |
spinningView.center = window.center | |
window.addSubview(spinningView) | |
spinningView.animate() | |
} else if !isLoading && window.subviews.contains(spinningView) { | |
spinningView.removeFromSuperview() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment