Ilya A. Shuvaloff Ilya A. Shuvaloff - 4 months ago 42
Swift Question

Update UI in UIAlertAction handler

Could you try to help me to solve the problem of updating the UI of my program while showing the UIAlertView instance?
That's the situation:


  1. I'm pressing the toolbar "hide-button" and the alertView is opening;

  2. In the handler of UIAlertAction (OK button) i have a code, where i make several operations:


    • remove the toolbar "hide-button" pressed and set the button item with activity indicator instead;

    • making the indicator rolling;

    • THEN AND ONLY AFTER PREVIOUS STEPS next part of code should start and the data model is being updated, and because it's connected to the tableView by means of NSFetchedResultsControllerDelegate, the tableView's data is gonna be updated automatically. This step can take some time, so it's extremely needed to hold it asynchronously, and while it's being processed the activity indicator should roll;

    • after that the activity indicator rolling faults, the toolbar button item with it is being removed and the "hide-button" (removed at the 1st step) comes back.
      FINISH.




The problem's with updating the UI, when i exchange "hide-button" and "activity-button".

private var hideDataBarButtonItem: UIBarButtonItem?
private var indicatorBarButtonItem = UIBarButtonItem(customView: UIActivityIndicatorView(activityIndicatorStyle: .Gray))

override func viewDidLoad() {
super.viewDidLoad()
...
hideDataBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Stop, target: self, action: #selector(hideAllLoginData))
toolbarItems?.insert(hideDataBarButtonItem!, atIndex: 2)
}


That's the action for hideDataBarButtonItem:

@IBAction func hideAllLoginData(sender: AnyObject) {
let confirmAlert = UIAlertController(title: "Hide all data?", message: "", preferredStyle: .Alert)

confirmAlert.addAction( UIAlertAction(title: "OK", style: .Default, handler: { action in
// remove the clear-button, set the indicator button instead and start indicator rolling
self.toolbarItems?.removeAtIndex(2)
self.toolbarItems?.insert(self.indicatorBarButtonItem, atIndex: 2)
(self.indicatorBarButtonItem.customView as! UIActivityIndicatorView).startAnimating()
print("button with indicator added")

sleep(5) // -> CHECK: this moment indicator should be visible and rolling!

dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
for section in self.resources {
for resource in section {
if resource.defRecord != nil {
resource.defRecord = nil
}
}
}
print("data cleared")

dispatch_async(dispatch_get_main_queue()) {
// remove indicator and set the clear-button back
print("button with indicator removed")
(self.indicatorBarButtonItem.customView as! UIActivityIndicatorView).stopAnimating()
self.toolbarItems?.removeAtIndex(2)
self.toolbarItems?.insert(self.hideDataBarButtonItem!, atIndex: 2)
}
}
}) )
confirmAlert.addAction( UIAlertAction(title: "Cancel", style: .Cancel, handler: nil ) )

self.presentViewController(confirmAlert, animated: true, completion: nil)
}


The result of the execution:

button with indicator added
// -> 5 sec of awaiting, but that's nothing in interface changed!
data cleared
button with indicator removed


If i don't remove the indicator button, i can see it after all, but it has to appear earlier. What do i make wrong?
Thank you!

Answer

The problem's solved. It was because the NSFetchedResultsController updated the UI not in the main thread. I had to deactivate it's delegate while my model was updating. Then, in the main thread i has to activate it again and update the tableView data. The solution is here (look at the comments):

        dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
            // clear the delegate in order to not to allow UI update by fetchedResultsController
            self.resourceModel.nullifyFetchedResultsControllerDelegate()
            for section in self.resources {
                for resource in section {
                    if resource.defRecord != nil {
                        resource.defRecord = nil
                    }
                }
            }
            print("data cleared")

            dispatch_async(dispatch_get_main_queue()) {
                // set the delegate back and update the tableView
                self.resourceModel.setFetchedResultsControllerDelegate(self)
                self.reloadResources()
                self.tableView.reloadData()

                // remove indicator and set the clear-button back
                print("button with indicator removed")
                (self.indicatorBarButtonItem.customView as! UIActivityIndicatorView).stopAnimating()
                self.toolbarItems?.removeAtIndex(2)
                self.toolbarItems?.insert(self.hideDataBarButtonItem!, atIndex: 2)
            }
        }

PeterK, thank you for your advices!