Search code examples
swiftswift4nsoperationqueueuiactivityindicatorview

UIActivityIndicator taking several seconds to be dismissed


I have some code that does some background work using OperationQueue, so I employed UIAlertController to manage a spinner while the operations run.

The problem is that the spinner says on the screen for quite a long time after dismiss() is called.

Below I show the code I'm running, and the output I'm getting when I run the code in the simulator.

This function is in the ViewController:

@IBAction func runTheTest(_ sender: Any) {

  // MARK: Define web interaction parameters
  let config = URLSessionConfiguration.default
  config.waitsForConnectivity = true
  let defaultSession = URLSession(configuration: config)
  let url = URL(string: "https://www.google.com")
  var getUrlRequest = URLRequest(url: url!)
  getUrlRequest.httpMethod = "GET"

  // MARK: operations CREATION
  let getFormOp = GetFormOperation(getRequest: getUrlRequest, defaultSession: defaultSession, credentials: ["test":"test"], viewController: self)
  let finishOp = FinishOperation(viewController: self)

  // MARK: operations SET DEPENDENCIES
  finishOp.addDependency(getFormOp) // finish op is dependent on get op finishing
  getFormOp.defineFollowOnOperation(finishOp) // This would be if we needed to share data between the two

  // MARK: start the SPINNER
  LoaderController.sharedInstance.showLoader(viewController: self, title: "Please Wait...", message: "Getting data from the web")

  // MARK: initiate the long-running set of operations
  let operationQueue = OperationQueue()
  operationQueue.addOperations([getFormOp, finishOp], waitUntilFinished: false)

  print("operations are running")
}

Here's the class that manages the activity indicator:

class LoaderController: NSObject {

  static let sharedInstance = LoaderController()
  private let activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
  private var start = DispatchTime.now()

  func showLoader(viewController: ViewController, title: String, message: String) {
    start = DispatchTime.now()
    activityIndicator.hidesWhenStopped = true
    activityIndicator.activityIndicatorViewStyle = .gray
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    self.activityIndicator.startAnimating()
    alert.view.addSubview(activityIndicator)
    viewController.present(alert, animated: true, completion: nil)
    print("load indicator presented")
  }

  func removeLoader(viewController: ViewController){
    let removeCalled = DispatchTime.now()
    let spinnerSeconds =  (removeCalled.uptimeNanoseconds - start.uptimeNanoseconds) / 1000000000
    print("load indicator remove request after \(spinnerSeconds) seconds.")
    viewController.dismiss(animated: true, completion: {
      let finallyGone = DispatchTime.now()
      let spinnerSeconds =  (finallyGone.uptimeNanoseconds - removeCalled.uptimeNanoseconds) / 1000000000
      print("load indicator gone after \(spinnerSeconds) additional seconds.")
    })
  }
}

The output looks like this:

load indicator presented
operations are running
load indicator remove request after 6 seconds.
load indicator gone after 4 additional seconds.

What I observe in the simulator aligns with the print statements; the operations complete, but the spinner remains for an additional 4 seconds.

What am I doing wrong here and how can I fix it?

I'm dedicated to improving this question if it lacks any attribute you deem necessary or deleting the question if that's appropriate, so please advise me using the comments.


Solution

  • removeLoader, as all UI operations, must be called from main thread only. If you are calling that method from an Operation (and not on OperationQueue.main), it should look like this:

    DispatchQueue.main.async {
       viewController.removeLoader(...)
    }
    

    That delay is almost always a hint that you are executing a UI operation from a background queue.

    It's also a good idea to turn on Main Thread Checker when debugging on simulator.