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.
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.