1

I am making a loading icon in my extension.swift file. I believe we do not need to use any third party library just for loading icons. However I cant store any values in the extension file. If I didnt store, it will look like

import UIKit

extension UIViewController {

func startLoading() {
    let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
    activityIndicator.center = self.view.center
    activityIndicator.hidesWhenStopped = true
    activityIndicator.style = .gray
    DispatchQueue.main.async {
        self.view.addSubview(activityIndicator)
    }
    activityIndicator.startAnimating()
    UIApplication.shared.beginIgnoringInteractionEvents()
}

func stopLoading() {
    let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
    DispatchQueue.main.async {
        activityIndicator.stopAnimating()
    }
    UIApplication.shared.endIgnoringInteractionEvents()
  }
}

So in your view controller, you can directly call startLoading() or stopLoading(). However the stopLoading doesnt work. It is because it's not accessing the variable initiated by startLoading. Any ways to go about this? Thanks alot guys!

4 Answers 4

2

You re-create the activity indicator so that's why it's not working.

Also you have the issue of transitioning to a new viewController hence self.view will not contain an activity indicator anymore.

So what i suggest you do is you create another UIWindow and lay on top, add a view and activity indicator to it and then when stopLoading() is called, you look for that UIWindow and remove the entire window if it exist.

Sign up to request clarification or add additional context in comments.

5 Comments

startLoading does work though. I am unable to stopLoading
Yes i know, i told you, you create another activity indicator in your stop function. That one isn't the same
It would be like you create a viewA and give it background color blue then you create viewB and give it background color red and you wonder why viewA isn't red it doesn't make any sense
@L.William startLoading will work because you are going through the correct steps. stopLoading doesn't work because your first line let activityIndicator = UIActivitiyIndicatorView() creates an entirely new activityIndicator. It is not retrieving the one you created in startLoading, it's a completely separate activityIndicator that is being created and then stopped.
Ok, understood. Hahaha, after so many explanations. Thanks @Vollan and Alan S
2

You can use store activityIndicator as a static variable, so you will get the same object in both calls:

extension UIViewController {

    static let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()

    func startLoading() {
        let activityIndicator = UIViewController.activityIndicator
        activityIndicator.center = self.view.center
        activityIndicator.hidesWhenStopped = true
        activityIndicator.style = .gray
        DispatchQueue.main.async {
            self.view.addSubview(activityIndicator)
        }
        activityIndicator.startAnimating()
        UIApplication.shared.beginIgnoringInteractionEvents()
    }

    func stopLoading() {
        let activityIndicator = UIViewController.activityIndicator
        DispatchQueue.main.async {
            activityIndicator.stopAnimating()
            activityIndicator.removeFromSuperview()
        }
        UIApplication.shared.endIgnoringInteractionEvents()
      }
}

The downswide of this approach is that this activityIndicator will be stored in memory even you hide it (after calling stopLoading()).

Comments

2

You can do this if you're determined to have the implementation be on the view of the ViewController and you won't always have an Activity Indicator loaded in your controller. But @Vollan's answer is the correct approach to doing this, or to use a package like SVProgressHUD (which basically creates a UIWindow with a loader in it)

class ViewController: ActivityIndicatorController {
   override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        startLoading()    
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            self.stopLoading()
        }
    }
}


class ActivityIndicatorController: UIViewController {
    var activityIndicator: UIActivityIndicatorView?

    func startLoading() {
        activityIndicator = UIActivityIndicatorView()
        activityIndicator?.center = self.view.center
        activityIndicator?.hidesWhenStopped = true
        activityIndicator?.style = .gray
        DispatchQueue.main.async {
            if let activityIndicator = self.activityIndicator {
               self.view.addSubview(activityIndicator)
               activityIndicator.startAnimating()
            }
        }
        UIApplication.shared.beginIgnoringInteractionEvents()
    }

    func stopLoading() {
        DispatchQueue.main.async {
            self.activityIndicator?.stopAnimating()
            self.activityIndicator?.removeFromSuperview()
            self.activityIndicator = nil
        }
        UIApplication.shared.endIgnoringInteractionEvents()
    }
}

2 Comments

If you call stopLoading() before startLoading() by mistake the app will crash. Using force unwrap (!) is highly discouraged. It will be used of (?) : self.activityIndicator?.stopAnimating(). Also, you are not removing UIActivityIndicatorView after stoping and create each time now one in startLoading, so the previous one still exists but it is hidden.
@bazyl87 yeah fair enough, I just copied OPs code and quick fixed it tbh. Didn't think of the entire use case
2

Associate a unique tag with your UIActivityIndicatorView.
In stopLoading(), use viewWithTag(_:) to find activity indicator view and call stopAnimating on that.

extension UIViewController {
    static var activityIndicatorTag = 12345

    func startLoading() {
        stopLoading()

        let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
        activityIndicator.tag = UIViewController.activityIndicatorTag
        activityIndicator.center = view.center
        activityIndicator.hidesWhenStopped = true
        activityIndicator.style = .gray

        DispatchQueue.main.async {
            self.view.addSubview(activityIndicator)
            activityIndicator.startAnimating()
        }
    }

    func stopLoading() {
        let activityIndicator = view.viewWithTag(UIViewController.activityIndicatorTag) as? UIActivityIndicatorView
        DispatchQueue.main.async {
            activityIndicator?.stopAnimating()
            activityIndicator?.removeFromSuperview()
        }
    }
}

2 Comments

In stopLoading() the activityIndicator is not removed from view, so it could lead to a problems as there could be a multiple activityIndicator with the same tag. You can add activityIndicator?.removeFromSuperview() after activityIndicator?.stopAnimating()
Yes definitely, it should be removed. Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.