My code is performing a lengthy, async operation (HTTP). Before starting it, I present a custom modal view controller with a spinning activity indicator. On completion, I dismiss it and present an alert controller to inform the user of task completion.
My code looks like this:
// This is a custom modal view controller. It mimics the modal
// presentation style of UIAlertController, but contains a label
// and a spinner:
let progress = ActivityIndicatorAlertController(withTitle:"Syncing...")
self.presentViewController(progress, animated:true, completion:{
WebService.sharedService.syncData(data, completion: {error in
self.dismissViewControllerAnimated(false, completion: {
// <BREAKPOINT>
if error == nil {
// Update UI:
self.reloadTable()
// This method presents a vanilla UIAlertController
// with the specified title and an OK
// button to dismiss:
self.presentModalInformation(title:"Sync Complete", message:"")
Immediately after the last line above executes, I get this warning on the console:
Warning: Attempt to present <UIAlertController: 0x7fae3a57ad60> on <AppName.MyPresentingViewController: 0x7fae3b02a800> which is already presenting (null)
Despite the dismiss completion handler being executed (location marked <BREAKPOINT> in the code above), the first modal view controller (progress indicator) stays onscreen, and (of course) the "completion" view controller is never displayed.
To the eyes of the user, the sync operation "freezes" and the app becomes unresponsive (hung on modal presentation).
Some Observations:
For completeness, here is the method that presents the second modal view controller:
func presentModalInformation(title title:String, message:String) { let alertController = UIAlertController( title: title, message: message, preferredStyle: .Alert) let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil) alertController.addAction(okAction) self.presentViewController(alertController, animated: true, completion: nil) }This issue started happening at some moment I can not pin-point, during modifications to the tasks performed via HTTP. I didn't notice the exact moment because I am on a slow connection and testing different data sets every time.
UIKit complains that there already is a view controller presented modally. Indeed, if I stop execution at the location marked
<BREAKPOINT>in the code above, and inspect variables via the console, I get:(lldb) print self.presentedViewController?.classForCoder (AnyClass?) $R1 = AppName.ActivityIndicatorAlertController
...despite all of this occurring inside the dismiss completion handler.
I tried to get away with a
dispatch_after()of one second, to delay the presentation of the second modal view controller (perhaps to give the first one "time" to dismiss properly?) but it doesn't work. The first one just doesn't get dismissed!<BREAKPOINT>in the code above is executed on the main thread ("com.apple.main-thread (serial)").Inserting a second call to
dismiss..., as the first thing inside the completion handler, seems to fix the issue:self.presentViewController(progress, animated:true, completion:{ WebService.sharedService.syncData(data, completion: {error in self.dismissViewControllerAnimated(false, completion: { // <BREAKPOINT> self.dismissViewControllerAnimated(false, completion:nil) // ^^ THIS "FIXES" IT ^^
...What could be going on?
Addendum: The source code for ActivityIndicatorAlertController is in this repository.
alertControllerin a new window, or pass along the parentViewController to the closure to use to present it - Tj3npresentViewControlleranddismissViewController. Just put your code straight after the present/dismiss and see how it goes. - Paulw11