Writing an app with some network activity I find myself writing the same code for multiple view controllers over and over just to display an activity indicator.
class SomeViewController: UIViewController {
let indicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
override func viewDidLoad() {
super.viewDidLoad()
// customize indicator
self.indicator.layer.cornerRadius = 10
self.indicator.center = self.view.center
self.indicator.hidesWhenStopped = true
self.indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
self.indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
}
// MARK: - Acitivity Indicator
func startIndicatingActivity() {
DispatchQueue.main.async {
self.view.addSubview(self.indicator)
self.indicator.startAnimating()
}
}
func stopIndicatingActivity() {
DispatchQueue.main.async {
self.indicator.stopAnimating()
}
}
}
Within the same SomeViewController
class I can then use it as follows:
@IBAction func startHeavyNetworkStuffButtonPressed(_ sender: UIButton) {
startIndicatingActivity()
doHeavyNetworkStuff() { success in
// heavy networking has finished
stopIndicatingActivity()
}
}
This works fine as long as I only need to display the activity indicator in a single view controller. However, it's tedious to do it over and over for every view controller that needs this functionality. As I hate writing the same code over and over, I am in search of a solution where I can simply call
startIndicatingActivity()
(and stopIndicatingActivity()
respectively) in any view controller.
My obvious first thought was to write an extension for the UIViewController
class. As I need to store an instance of the UIActivityIndicatorView
, however, I got the Extensions may not contain stored properties
error.
Next up: subclassing UIViewController
. This would work fine for any simple view controller. However, if I needed the same functionality for a MyCustomTableViewController
, I would again need to first subclass from UITableViewController
and copy/paste existing code.
Is there an elegant way to call startIndicatingActivity()
/ stopIndicatingActivity()
in any view controller while avoiding to copy/paste large amounts of code? I'm assuming an elegant solution would involve an extension
, protocol
, or some kind of multiple-inheritance approach.
This SO thread is the solution! Turns out there is a way to solve this with an extension
and simulated properties, after all.
Posting the complete solution for the interested reader:
UIViewController
extension UIViewController {
// see ObjectAssociation<T> class below
private static let association = ObjectAssociation<UIActivityIndicatorView>()
var indicator: UIActivityIndicatorView {
set { UIViewController.association[self] = newValue }
get {
if let indicator = UIViewController.association[self] {
return indicator
} else {
UIViewController.association[self] = UIActivityIndicatorView.customIndicator(at: self.view.center)
return UIViewController.association[self]!
}
}
}
// MARK: - Acitivity Indicator
public func startIndicatingActivity() {
DispatchQueue.main.async {
self.view.addSubview(self.indicator)
self.indicator.startAnimating()
//UIApplication.shared.beginIgnoringInteractionEvents() // if desired
}
}
public func stopIndicatingActivity() {
DispatchQueue.main.async {
self.indicator.stopAnimating()
//UIApplication.shared.endIgnoringInteractionEvents()
}
}
}
// source: https://stackoverflow.com/questions/25426780/how-to-have-stored-properties-in-swift-the-same-way-i-had-on-objective-c
public final class ObjectAssociation<T: AnyObject> {
private let policy: objc_AssociationPolicy
/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
self.policy = policy
}
/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {
get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}
I also extended the UIActivityIndicatorView
class with a static function with my customizations.
extension UIActivityIndicatorView {
public static func customIndicator(at center: CGPoint) -> UIActivityIndicatorView {
let indicator = UIActivityIndicatorView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
indicator.layer.cornerRadius = 10
indicator.center = center
indicator.hidesWhenStopped = true
indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.whiteLarge
indicator.backgroundColor = UIColor(red: 1/255, green: 1/255, blue: 1/255, alpha: 0.5)
return indicator
}
}
Sample show full loading in any View
extension UIView{
/**
ShowLoader: loading view ..
- parameter Color: ActivityIndicator and view loading color .
*/
func showLoader(_ color:UIColor?){
let LoaderView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
LoaderView.tag = -888754
LoaderView.backgroundColor = color
let Loader = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 60, height: 30))
Loader.center = LoaderView.center
Loader.activityIndicatorViewStyle = .whiteLarge
Loader.color = Color.primaryColor
Loader.startAnimating()
LoaderView.addSubview(Loader)
self.addSubview(LoaderView)
}
/**
dismissLoader: hidden loading view ..
*/
func dismissLoader(){
self.viewWithTag(-888754)?.removeFromSuperview()
}
}
call this func
in UIViewController
self.view.showLoader(nil) //you can set background color nil or any color
// dismiss
self.view.dismissLoader()
I prefer this method because you can use it any view in
button , tableview , cell ...etc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With