Skip to content

Instantly share code, notes, and snippets.

@Zhendryk
Created January 13, 2019 03:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zhendryk/1400a933410ffaba8ca7bbcbdaab85b3 to your computer and use it in GitHub Desktop.
Save Zhendryk/1400a933410ffaba8ca7bbcbdaab85b3 to your computer and use it in GitHub Desktop.
Custom activity indicator that natively renders After Effects vector animations with an optional message NOTE: This depends on airbnb/lottie-ios which can be found here: https://github.com/airbnb/lottie-ios
//
// CustomActivityIndicator.swift
//
// Created by Jonathan Bailey on 9/10/18.
// Copyright © 2018 Jonathan Bailey. All rights reserved.
//
import UIKit
import Lottie
class CustomActivityIndicator: UIView {
/// MARK: - Public variables
/// The label containing the title text of the view
lazy var titleLabel: UILabel = UILabel()
/// The label containing the message text of the view
lazy var messageLabel: UILabel = UILabel()
/// The LOTAnimation view which runs Adobe After Effects animations
lazy var animationView: LOTAnimationView = LOTAnimationView()
/// Whether or not this view will fade out when animation stops
@IBInspectable var hidesWhenStopped: Bool = true
/// If this view has a title and message on it, or just an animation
@IBInspectable var textEnabled: Bool = true
/// MARK: - Private variables
private let container: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .fill
stackView.spacing = 5
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 0, right: 10)
return stackView
}()
/// Instantiates a CustomActivityIndicator with the given title, message and animation
///
/// - Parameters:
/// - title: The title for the view
/// - message: The message for the view
/// - animation: The name of the lottie animation .json file (no extension)
convenience init(title: String, message: String, animation: String) {
self.init()
self.titleLabel.text = title
self.messageLabel.text = message
self.animationView = LOTAnimationView(name: animation)
setupViews()
}
/// Instantiates a CustomActivityIndicator with only the given animation, text is disabled
///
/// - Parameter animation: The filename of the lottie animation .json file (no extension)
convenience init(animation: String) {
self.init()
self.textEnabled = false
self.animationView = LOTAnimationView(name: animation)
setupViews()
}
override var intrinsicContentSize: CGSize {
let value = Swift.min(UIScreen.main.bounds.size.width/2, UIScreen.main.bounds.size.height/2)
return CGSize(width: value, height: value)
}
/// Begin animating and loop the animation until stopAnimating is called
func startAnimating() {
fadeIn()
animationView.loopAnimation = true
animationView.play()
}
/// Stop the view from animating and fade out if hidesWhenStopped is true
func stopAnimating() {
animationView.stop()
if hidesWhenStopped {
fadeOut()
}
}
/// Animate the view through a single loop and stop with its current settings
func playOnce() {
if animationView.isAnimationPlaying {
animationView.stop()
}
animationView.loopAnimation = false
self.fadeIn()
animationView.play { finished in
if finished {
self.stopAnimating()
}
}
}
/// Set the title, message and animation of the view to loop once and run the completion handler when the animation is finished
///
/// - Parameters:
/// - title: The new title of the view
/// - message: The new message of the view
/// - animation: The new animation of the view
/// - completion: The code to run after the animation is complete
func playOnce(title: String, message: String, animation: String, _ completion: (@escaping () -> ()) = {}) {
if animationView.isAnimationPlaying {
animationView.stop()
}
self.titleLabel.text = title
self.messageLabel.text = message
animationView.setAnimation(named: animation)
animationView.loopAnimation = false
self.fadeIn()
animationView.play { finished in
if finished {
self.stopAnimating()
completion()
}
}
}
/// Set new text for the view
///
/// - Parameters:
/// - title: The new title
/// - body: The new message
func setText(title: String, message: String) {
self.titleLabel.text = title
self.messageLabel.text = message
}
/// Set a new animation to the view
///
/// - Parameter animation: The filename of the lottie animation .json file (no extension)
func setAnimation(animation: String) {
self.animationView.setAnimation(named: animation)
}
private func setupViews() {
backgroundColor = .darkGray
isOpaque = false
alpha = 0.0
layer.cornerRadius = 15
isUserInteractionEnabled = false
if textEnabled {
titleLabel.textColor = .white
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 1
titleLabel.font = UIFont.boldSystemFont(ofSize: 24)
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
container.addArrangedSubview(titleLabel)
messageLabel.textColor = .white
messageLabel.textAlignment = .center
messageLabel.adjustsFontSizeToFitWidth = true
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.font = UIFont.systemFont(ofSize: 17)
messageLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
messageLabel.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
container.addArrangedSubview(messageLabel)
}
animationView.contentMode = .scaleAspectFit
animationView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
animationView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
container.addArrangedSubview(animationView)
addSubview(container)
setupLayout()
}
private func setupLayout() {
container.translatesAutoresizingMaskIntoConstraints = false
container.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor).isActive = true
container.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor).isActive = true
container.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor).isActive = true
container.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor).isActive = true
}
}
extension UIView {
func fadeIn(withDuration duration: TimeInterval = 0.5) {
UIView.animate(withDuration: duration, animations: {
self.alpha = 1.0
})
}
func fadeOut(withDuration duration: TimeInterval = 0.5) {
UIView.animate(withDuration: duration) {
self.alpha = 0.0
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment