So I want to make a button animate on press to go to a circle, and then be able to send the button back to its original state. This is my current animation, and as you can see I'm halfway there.
As you also can see I'm having multiple issues here. First of all, when I set my new constraints the X constraint doesn't place the circle in the middle of the parent view. And then my initial thought was that when i call the reset function, that I would also pass the original constraints of the view, but that just isn't working.
My idea is that when i'm using it i'll put a UIView and then have the button inside that view, so I can manipulate the constraints of it. This would also be the case if i'm putting a button in a UIStackView.
extension UIButton {
func animateWhileAwaitingResponse(showLoading: Bool, originalConstraints: [NSLayoutConstraint]) {
let spinner = UIActivityIndicatorView()
let constraints = [
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45),
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: self.superview, attribute: .top, multiplier: 1, constant: 4),
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: self.superview, attribute: .bottom, multiplier: 1, constant: 8),
NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
NSLayoutConstraint(item: spinner, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45)
]
if showLoading {
NSLayoutConstraint.deactivate(self.constraints)
self.translatesAutoresizingMaskIntoConstraints = false
spinner.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(spinner)
self.superview?.addConstraints(constraints)
spinner.color = .white
spinner.startAnimating()
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.setTitleColor(.clear, for: .normal)
self.layer.cornerRadius = 22.5
self.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
NSLayoutConstraint.deactivate(self.constraints)
self.setTitleColor(.white, for: .normal)
self.superview?.addConstraints(originalConstraints)
NSLayoutConstraint.activate(originalConstraints)
self.layer.cornerRadius = 0
for subview in self.subviews where subview is UIActivityIndicatorView {
subview.removeFromSuperview()
}
self.layoutIfNeeded()
}, completion: nil)
}
}
}
I have updated your button extension code as follow, which is adding and removing constraints with animation.
extension UIButton {
func animateWhileAwaitingResponse(showLoading: Bool, originalConstraints: [NSLayoutConstraint]) {
let spinner = UIActivityIndicatorView()
spinner.isUserInteractionEnabled = false
// Constraints which will add in supper view
let constraints = [
NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: self.superview, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: spinner, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
NSLayoutConstraint(item: spinner, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45)
]
// Constrains which will add in button
let selfCostraints = [
NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 45),
NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 45),
]
// Keeping this outside of condition due to adding constrains programatically.
self.translatesAutoresizingMaskIntoConstraints = false
spinner.translatesAutoresizingMaskIntoConstraints = false
if showLoading {
// Remove width constrains of button from superview
// Identifier given in storyboard constrains
self.superview?.constraints.forEach({ (constraint) in
if constraint.identifier == "buttonWidth" {
constraint.isActive = false
}
})
NSLayoutConstraint.deactivate(self.constraints)
self.addSubview(spinner)
self.superview?.addConstraints(constraints)
self.addConstraints(selfCostraints)
spinner.color = .white
spinner.startAnimating()
spinner.alpha = 0
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.setTitleColor(.clear, for: .normal)
self.layer.cornerRadius = 22.5
spinner.alpha = 1
self.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
for subview in self.subviews where subview is UIActivityIndicatorView {
subview.removeFromSuperview()
}
self.removeConstraints(selfCostraints)
NSLayoutConstraint.deactivate(self.constraints)
self.setTitleColor(.white, for: .normal)
self.superview?.addConstraints(originalConstraints)
NSLayoutConstraint.activate(originalConstraints)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
}
}
I have added following constrains to button:
Also, added identifier of button's width constraint to remove from super which will add runtime from original constrains.
Then I have change width of button programatically by taking outlet of width constrains:
@IBOutlet weak var const_btnAnimation_width : NSLayoutConstraint!
inside viewDidLoad
method
self.const_btnAnimation_width.constant = UIScreen.main.bounds.width - 40
where 40 is sum of leading and trailing space.
on button click
@IBAction func btnAnimationPressed(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
self.btnAnimation.animateWhileAwaitingResponse(showLoading: true, originalConstraints: sender.constraints)
} else {
self.btnAnimation.animateWhileAwaitingResponse(showLoading: false, originalConstraints: self.btnAnimationConstraints)
}
}
btnAnimationConstraints
is array of NSLayoutConstraint
as follow:
var btnAnimationConstraints = [NSLayoutConstraint]()
So I just assign all constrains of button inside viewDidAppear
method as follow:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.btnAnimationConstraints = self.btnAnimation.constraints
}
I hope this will help you.
Output: