SetupIndicatorView.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. //
  2. // SetupIndicatorView.swift
  3. // Loop
  4. //
  5. // Copyright © 2018 LoopKit Authors. All rights reserved.
  6. //
  7. import UIKit
  8. @IBDesignable
  9. public class SetupIndicatorView: UIView {
  10. public enum State: Equatable {
  11. case hidden
  12. case indeterminantProgress
  13. case timedProgress(finishTime: CFTimeInterval)
  14. case completed
  15. }
  16. public var state: State = .hidden {
  17. didSet {
  18. animate(from: oldValue, to: state)
  19. if case let .timedProgress(finishTime) = state {
  20. let duration = finishTime - CACurrentMediaTime()
  21. progressView.progress = 0
  22. if duration > 0 {
  23. let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
  24. animator.addAnimations {
  25. self.progressView.setProgress(1, animated: true)
  26. }
  27. animator.startAnimation()
  28. self.progressAnimator = animator
  29. } else {
  30. progressView.progress = 1
  31. }
  32. }
  33. }
  34. }
  35. @IBInspectable var animationDuration: Double = 0.5
  36. private func viewUsedInState(_ state: State) -> UIView? {
  37. switch state {
  38. case .hidden:
  39. return nil
  40. case .indeterminantProgress:
  41. return activityIndicatorView
  42. case .timedProgress:
  43. return progressView
  44. case .completed:
  45. return completionImageView
  46. }
  47. }
  48. private func animate(from oldState: State, to newState: State) {
  49. guard oldState != newState else {
  50. return
  51. }
  52. if let animator = self.animator, animator.isRunning {
  53. animator.stopAnimation(true)
  54. }
  55. // Figure out which views are not used in any ongoing animations
  56. var unusedViews: Set = [activityIndicatorView, progressView, completionImageView]
  57. let viewToHide = viewUsedInState(oldState)
  58. unusedViews.remove(viewToHide)
  59. let viewToShow = viewUsedInState(newState)
  60. unusedViews.remove(viewToShow)
  61. for view in unusedViews {
  62. view?.alpha = 0
  63. }
  64. if case .timedProgress = oldState {
  65. progressAnimator?.stopAnimation(true)
  66. }
  67. let animator = UIViewPropertyAnimator(duration: animationDuration, dampingRatio: 0.5)
  68. animator.addAnimations {
  69. viewToHide?.alpha = 0
  70. viewToShow?.alpha = 1
  71. switch oldState {
  72. case .completed:
  73. self.completionImageView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
  74. default:
  75. break
  76. }
  77. switch newState {
  78. case .indeterminantProgress:
  79. self.activityIndicatorView.startAnimating()
  80. case .completed:
  81. self.completionImageView.transform = CGAffineTransform.identity
  82. default:
  83. break
  84. }
  85. }
  86. animator.addCompletion { (position) in
  87. if self.state != .indeterminantProgress {
  88. self.activityIndicatorView.stopAnimating()
  89. }
  90. }
  91. animator.startAnimation()
  92. self.animator = animator
  93. }
  94. private var progressAnimator: UIViewPropertyAnimator?
  95. private var animator: UIViewPropertyAnimator?
  96. private let activityIndicatorView = UIActivityIndicatorView(style: .default)
  97. private let progressView = UIProgressView(progressViewStyle: .default)
  98. private(set) var completionImageView: UIImageView!
  99. override init(frame: CGRect) {
  100. super.init(frame: frame)
  101. setUp()
  102. }
  103. required public init?(coder aDecoder: NSCoder) {
  104. super.init(coder: aDecoder)
  105. setUp()
  106. }
  107. private func setUp() {
  108. let image = UIImage(named: "Checkmark", in: Bundle(for: type(of: self)), compatibleWith: traitCollection)!
  109. completionImageView = UIImageView(image: image)
  110. completionImageView.alpha = 0
  111. completionImageView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
  112. completionImageView.translatesAutoresizingMaskIntoConstraints = false
  113. activityIndicatorView.alpha = 0
  114. activityIndicatorView.hidesWhenStopped = true
  115. activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
  116. progressView.alpha = 0
  117. progressView.translatesAutoresizingMaskIntoConstraints = false
  118. addSubview(activityIndicatorView)
  119. addSubview(progressView)
  120. addSubview(completionImageView)
  121. NSLayoutConstraint.activate([
  122. heightAnchor.constraint(equalToConstant: image.size.height),
  123. completionImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
  124. completionImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
  125. activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor),
  126. activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor),
  127. progressView.centerXAnchor.constraint(equalTo: centerXAnchor),
  128. progressView.centerYAnchor.constraint(equalTo: centerYAnchor),
  129. progressView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10),
  130. progressView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10),
  131. ])
  132. }
  133. override public var intrinsicContentSize: CGSize {
  134. return completionImageView?.image?.size ?? activityIndicatorView.intrinsicContentSize
  135. }
  136. }