Skip to content

Commit

Permalink
fix: fix loader issue
Browse files Browse the repository at this point in the history
fix the issue that was happening if stopAnimating was called right after startAnimating.
  • Loading branch information
O-labib committed May 24, 2021
1 parent aceebe9 commit 3150660
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 56 deletions.
13 changes: 7 additions & 6 deletions SpinningButton/Classes/Behaviors/BaseBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ public enum LoaderPosition: Equatable {
}

class BaseBehavior {

weak var button: SpinningButton?

init(button: SpinningButton) {
self.button = button
}

func startAnimating() {}
func stopAnimating() {}


func startAnimating() {
}
func stopAnimating() {
}

}

extension BaseBehavior {
Expand Down
104 changes: 66 additions & 38 deletions SpinningButton/Classes/Behaviors/CentreLoadingBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,112 +8,141 @@
import UIKit

class CentreLoadingBehavior: BaseBehavior {

private var shouldShrinkOnLoading: Bool
private var cachedTitle: String? = ""
private var cachedCornerRadius: CGFloat = 0.0
private var cachedImage: UIImage?

private var fullyShrunk = false
private var animationCompleted = false

init(button: SpinningButton, shrinkOnLoading: Bool = false) {
self.shouldShrinkOnLoading = shrinkOnLoading
super.init(button: button)
}

override func startAnimating() {
guard let button = button else {return}

guard let button = button else { return }
setupConstraint()
cacheState()
button.clearTitleAndImage()
animateSpinnerAndShrinkButton()
}

private func setupConstraint() {
guard let button = button else { return }
button.addSubview(button.loadingSpinner)
button.loadingSpinner.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true

if shouldShrinkOnLoading {
let offset = (button.frame.height - 20) / 2
button.loadingSpinner.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: offset).isActive = true
} else {
button.loadingSpinner.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
}
}

private func cacheState() {
self.cachedTitle = button?.title(for: .normal)
self.cachedImage = button?.image(for: .normal)
self.cachedCornerRadius = button?.layer.cornerRadius ?? 0
}

private func animateSpinnerAndShrinkButton() {
animationCompleted = false
fullyShrunk = false

button?.loadingSpinner.transform = CGAffineTransform(scaleX: 0, y: 0)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.45, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.button?.loadingSpinner.transform = CGAffineTransform(scaleX: 1, y: 1)
}) { _ in
UIView.animate(withDuration: 0.3,
delay: 0,
usingSpringWithDamping: 0.45,
initialSpringVelocity: 0,
options: .curveEaseInOut,
animations: {
self.button?.loadingSpinner.transform = CGAffineTransform(scaleX: 1, y: 1)
}) { _ in
self.button?.loadingSpinner.startAnimating()

self.animationCompleted = true
if self.shouldShrinkOnLoading {
self.button?.animateShrinking()
self.button?.animateShrinking(completion: {
self.fullyShrunk = true
})
}
}

}

override func stopAnimating() {
if button?.loadingSpinner.isAnimating != true {
return
let delay = animationCompleted ? 0 : 0.9
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.button?.loadingSpinner.stopAnimating()
self.restoreStateOfShrinkingButton()
self.restoreStateOfNormalButton()
}
button?.loadingSpinner.stopAnimating()
if shouldShrinkOnLoading {
button?.animateToOriginalWidth(completion: restoreCachedState)
} else {
restoreCachedState()
}
private func restoreStateOfShrinkingButton() {
guard self.shouldShrinkOnLoading else { return }

let delay = self.fullyShrunk ? 0 : 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
self.button?.animateToOriginalWidth(completion: self.restoreCachedState)
}
}

private func restoreStateOfNormalButton() {
guard !self.shouldShrinkOnLoading else { return }

self.button?.isUserInteractionEnabled = true
self.restoreCachedState()
}

private func restoreCachedState() {
button?.setTitle(cachedTitle, for: .normal)
button?.setImage(cachedImage, for: .normal)
UIView.animate(withDuration: 0.15) {
self.button?.layer.cornerRadius = self.cachedCornerRadius
}

}

}

private let shrinkCurve: CAMediaTimingFunction = CAMediaTimingFunction(name: .linear)
private let expandCurve: CAMediaTimingFunction = CAMediaTimingFunction(controlPoints: 0.95, 0.02, 1, 0.05)
private let shrinkDuration: CFTimeInterval = 0.25

private extension UIButton {

func clearTitleAndImage() {
setTitle(nil, for: .normal)
setImage(nil, for: .normal)
}

func animateShrinking() {
func animateShrinking(completion:(() -> Void)?) {
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.layer.cornerRadius = self.frame.height / 2
}, completion: { _ -> Void in
self.shrink()
self.shrink(completion: completion)
})
}

func shrink() {
func shrink(completion:(() -> Void)?) {
let shrinkAnimation = CABasicAnimation(keyPath: "bounds.size.width")
shrinkAnimation.fromValue = frame.width
shrinkAnimation.toValue = frame.height
shrinkAnimation.duration = shrinkDuration
shrinkAnimation.timingFunction = shrinkCurve
shrinkAnimation.fillMode = .forwards
shrinkAnimation.isRemovedOnCompletion = false

CATransaction.setCompletionBlock {
completion?()
}
layer.add(shrinkAnimation, forKey: shrinkAnimation.keyPath)


CATransaction.commit()

}

func animateToOriginalWidth(completion:(() -> Void)?) {
let shrinkAnim = CABasicAnimation(keyPath: "bounds.size.width")
shrinkAnim.fromValue = (self.bounds.height)
Expand All @@ -122,15 +151,14 @@ private extension UIButton {
shrinkAnim.timingFunction = shrinkCurve
shrinkAnim.fillMode = .forwards
shrinkAnim.isRemovedOnCompletion = false

CATransaction.setCompletionBlock {
// self.setNeedsLayout()
// self.setNeedsDisplay()
completion?()
self.isUserInteractionEnabled = true
}
self.layer.add(shrinkAnim, forKey: shrinkAnim.keyPath)

CATransaction.commit()
}

}
35 changes: 23 additions & 12 deletions SpinningButton/Classes/Behaviors/EdgeLoadingBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,39 @@
import UIKit

class EdgeLoadingBehavior: BaseBehavior {

private var offset: CGFloat
private var isTrailing = false

private var titleWhileLoading: String?
private var cachedTitle: String?


private var animationCompleted = false
init(button: SpinningButton, offset: CGFloat = 4.0, isTrailing: Bool, titleWhileLoading: String? ) {
self.offset = offset
self.isTrailing = isTrailing
self.titleWhileLoading = titleWhileLoading
super.init(button: button)
setupConstraints()
}

override func startAnimating() {
animationCompleted = false
guard let button = button else {return}
UIView.animate(withDuration: 0.3, delay: 0.05, usingSpringWithDamping: 0.45, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
button.loadingSpinner.transform = CGAffineTransform(scaleX: 1, y: 1)
}) { _ in
button.loadingSpinner.startAnimating()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.animationCompleted = true
}
if let titleWhileLoading = titleWhileLoading {
cachedTitle = button.title(for: .normal)
button.setTitle(titleWhileLoading, for: .normal)
}
}

private func setupConstraints() {
guard let button = button else {return}
button.addSubview(button.loadingSpinner)
Expand All @@ -47,15 +52,21 @@ class EdgeLoadingBehavior: BaseBehavior {
button.loadingSpinner.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: offset).isActive = true
}
}

override func stopAnimating() {
guard let button = button else {return}
button.loadingSpinner.stopAnimating()

if titleWhileLoading != nil {
button.setTitle(cachedTitle, for: .normal)

let delay = animationCompleted ? 0 : 1.0
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {

button.loadingSpinner.stopAnimating()

if self.titleWhileLoading != nil {
button.setTitle(self.cachedTitle, for: .normal)
}
button.isUserInteractionEnabled = true
}

}

}

0 comments on commit 3150660

Please sign in to comment.