I would like to change the content of a UIButton to an ActivityIndicator after it is pressed.
I know buttons have an imageView and a titleLabel, but I don\'t know how to
Code found here:
https://www.snip2code.com/Snippet/777050/iOS---Swift---UIButton-subclass-for-show/
import UIKit
class LoadingButton: UIButton {
private var originalButtonText: String?
var activityIndicator: UIActivityIndicatorView!
func showLoading() {
originalButtonText = self.titleLabel?.text
self.setTitle("", for: .normal)
if (activityIndicator == nil) {
activityIndicator = createActivityIndicator()
}
showSpinning()
}
func hideLoading() {
self.setTitle(originalButtonText, for: .normal)
activityIndicator.stopAnimating()
}
private func createActivityIndicator() -> UIActivityIndicatorView {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.hidesWhenStopped = true
activityIndicator.color = .lightGray
return activityIndicator
}
private func showSpinning() {
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(activityIndicator)
centerActivityIndicatorInButton()
activityIndicator.startAnimating()
}
private func centerActivityIndicatorInButton() {
let xCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: activityIndicator, attribute: .centerX, multiplier: 1, constant: 0)
self.addConstraint(xCenterConstraint)
let yCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator, attribute: .centerY, multiplier: 1, constant: 0)
self.addConstraint(yCenterConstraint)
}
}
Excellent @Steve,
Can use below implementation instead of line,
var activityIndicator: UIActivityIndicatorView!
use this, so that if we configure the activity indicator before calling showLoading(), the app wont crash.
private var _activityIndicator:UIActivityIndicatorView! = nil
var activityIndicator: UIActivityIndicatorView{
set{
_activityIndicator = newValue
}
get{
if _activityIndicator == nil{
_activityIndicator = createActivityIndicator()
}
return _activityIndicator
}
}
Swift 4.0 with a little modification
class LoadingUIButton: UIButton {
@IBInspectable var indicatorColor : UIColor = .lightGray
var originalButtonText: String?
var activityIndicator: UIActivityIndicatorView!
func showLoading() {
originalButtonText = self.titleLabel?.text
self.setTitle("", for: .normal)
if (activityIndicator == nil) {
activityIndicator = createActivityIndicator()
}
showSpinning()
}
func hideLoading() {
DispatchQueue.main.async(execute: {
self.setTitle(self.originalButtonText, for: .normal)
self.activityIndicator.stopAnimating()
})
}
private func createActivityIndicator() -> UIActivityIndicatorView {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.hidesWhenStopped = true
activityIndicator.color = indicatorColor
return activityIndicator
}
private func showSpinning() {
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(activityIndicator)
centerActivityIndicatorInButton()
activityIndicator.startAnimating()
}
private func centerActivityIndicatorInButton() {
let xCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: activityIndicator, attribute: .centerX, multiplier: 1, constant: 0)
self.addConstraint(xCenterConstraint)
let yCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator, attribute: .centerY, multiplier: 1, constant: 0)
self.addConstraint(yCenterConstraint)
}
}
Here is my edit with little less code.
class Button: UIButton {
private var originalButtonText: String?
private lazy var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.color = .black
addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerYAnchor.constraint(equalTo: self.centerYAnchor),
activityIndicator.centerXAnchor.constraint(equalTo: self.centerXAnchor)
])
return activityIndicator
}()
func loading(_ isLoading: Bool) {
isEnabled = !isLoading
if isLoading {
originalButtonText = titleLabel?.text
setTitle("", for: .normal)
activityIndicator.startAnimating()
} else {
setTitle(originalButtonText, for: .normal)
activityIndicator.stopAnimating()
}
}
}
Usage: button.loading(true/false)
Updated selected answer( steve-rosenberg ) for Apple Swift version 3.0.1 (swiftlang-800.0.58.6 clang-800.0.42.1) Xcode 8.1 (8B62)
import ObjectiveC
private var originalButtonText: String?
private var activityIndicator: UIActivityIndicatorView!
extension UIButton{
func showLoading() {
originalButtonText = self.titleLabel?.text
self.setTitle("", for: .normal)
if (activityIndicator == nil) {
activityIndicator = createActivityIndicator()
}
showSpinning()
}
func hideLoading() {
self.setTitle(originalButtonText, for: .normal)
activityIndicator.stopAnimating()
}
private func createActivityIndicator() -> UIActivityIndicatorView {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.hidesWhenStopped = true
activityIndicator.color = UIColor.lightGray
return activityIndicator
}
func showSpinning() {
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(activityIndicator)
centerActivityIndicatorInButton()
activityIndicator.startAnimating()
}
private func centerActivityIndicatorInButton() {
let xCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: activityIndicator, attribute: .centerX, multiplier: 1, constant: 0)
self.addConstraint(xCenterConstraint)
let yCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator, attribute: .centerY, multiplier: 1, constant: 0)
self.addConstraint(yCenterConstraint)
}
}
@Boris: This should not be in an extension.
Here it is in swift 3/4, with improved code: disables button, works with images and titles.
class LoadingButton: UIButton {
struct ButtonState {
var state: UIControlState
var title: String?
var image: UIImage?
}
private (set) var buttonStates: [ButtonState] = []
private lazy var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.hidesWhenStopped = true
activityIndicator.color = self.titleColor(for: .normal)
self.addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
let xCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: activityIndicator, attribute: .centerX, multiplier: 1, constant: 0)
let yCenterConstraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator, attribute: .centerY, multiplier: 1, constant: 0)
self.addConstraints([xCenterConstraint, yCenterConstraint])
return activityIndicator
}()
func showLoading() {
activityIndicator.startAnimating()
var buttonStates: [ButtonState] = []
for state in [UIControlState.disabled] {
let buttonState = ButtonState(state: state, title: title(for: state), image: image(for: state))
buttonStates.append(buttonState)
setTitle("", for: state)
setImage(UIImage(), for: state)
}
self.buttonStates = buttonStates
isEnabled = false
}
func hideLoading() {
activityIndicator.stopAnimating()
for buttonState in buttonStates {
setTitle(buttonState.title, for: buttonState.state)
setImage(buttonState.image, for: buttonState.state)
}
isEnabled = true
}
}