1
votes

Xcode version: 7.2.1, iOS version: 9.1+

I am trying to display a UIAlertController alert message on the iPad that shows a "Loading...Please Wait" message without any buttons on it. I present the UIAlertController before starting a long operation, and then after the long operation, I dismiss the UIAlertController. However, what is happening is the UIAlertController DOES NOT show up immediately. It only flashes up briefly AFTER the longer operation is completed, and then it is dismissed.

Following is the general code structure:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Loading" message:@"Please wait...\n\n\n" preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];

    /// Perform long operation here...
    /// (Basically, a message is being sent to a server, a long operation happens on 
    /// the server, and then the server returns a response to the iPad client.)

    [alert dismissViewControllerAnimated:YES completion:nil];
}

I have tried a number of alternatives, like using dispatch_async so that the alert message can be displayed simultaneously while the long operation is occurring:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Loading Dataset" message:@"Please wait...\n\n\n" preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{          

        /// Perform long operation here...

        dispatch_async(dispatch_get_main_queue(), ^{

            [alert dismissViewControllerAnimated:YES completion:nil];
        });
    });
}

I've tried using UIAlertView (which I know is deprecated in iOS 8), both alone and with dispatch, but this is not working either.

I've tried wrapping the long operation code in a performSelector message, but to no avail.

In the past, using a UIAlertView with a dispatch_queue has worked seamlessly for this situation.

Any assistance is appreciated.

Edit:

One interesting thing to note: In the dispatch_async code, if I add a usleep(250000) right before calling the long operation code, about 80% of the time, the UIAlertController alert message WILL be displayed at the correct time: BEFORE the long operation begins. However, this is not 100% of the time, and is not a sustainable solution. Calling usleep with a smaller number does not work.

Note that for the DISPATCH_QUEUE_PRIORITY_DEFAULT option, I also tried: DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_PRIORITY_LOW, and DISPATCH_QUEUE_PRIORITY_BACKGROUND

And, I tried the following too:

dispatch_queue_t myQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);

dispatch_async(myQueue, ^{
    /// Perform long operation here...
    dispatch_async(dispatch_get_main_queue(), ^{
        [alert dismissViewControllerAnimated:YES completion:nil];
    });
});
3
Your second variant is correct, you should post more code about your long operation. It is possible that you are blocking the main thread somewhere inside the long operation. That would cause exactly this problem.Sulthan
@Sulthan Yes, in the long operation code, I'm using TCP sockets to send and recv data. The recv socket function does block until it has received its data from the server.silverness
You are doing it on the background thread (in the second version) so that shouldn't be a problem. I guess you are somehow also blocking the main thread there.Sulthan

3 Answers

1
votes

What ended up working was a combination of the dispatch code and the CFRunLoopWakeUp call (suggested by @Bhavuk Jain). What I also had to do was move the code that was originally after the dispatch block (which I did not show in the original post) to INSIDE the dispatch block. So, the following is working thus far:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // Initialize and present the alert view controller.
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Loading Dataset" message:@"Please wait...\n\n\n" preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController:alert animated:YES completion:nil];

    // Force wake up the run loop.
    CFRunLoopWakeUp(CFRunLoopGetCurrent());

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{          

        /// Perform long operation here...

        dispatch_async(dispatch_get_main_queue(), ^{

            [alert dismissViewControllerAnimated:YES completion:nil];

            /// Execute rest of code INDSIDE the dispatch code block INSTEAD of after it...
        });
    });
}
1
votes

In the original post I marked as the answer (May 31), there were a few cases where the AlertViewController message would just hang because the dismissViewController method would not get called. (Perhaps some race condition in the various threads...I'm unsure.) So what I finally found to work reliably and appears to be a cleaner solution is to use a combination of the AlertViewController's completion block and a dispatch_after call.

More on the completion call from the iOS reference: "The completion handler gets called after the viewDidAppear: method is called on the presented view controller."

(This post was of assistance.)

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // Initialize the alert view controller.
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Loading Dataset" message:@"Please wait...\n\n\n" preferredStyle:UIAlertControllerStyleAlert];

    // Present the alert view controller.
    [self presentViewController:alert animated:YES completion:^{

        /// Perform long operation here...

        dispatch_after(0, dispatch_get_main_queue(), ^{

            // Dismiss the alert message.
            [loadAlertController dismissViewControllerAnimated:YES completion:nil];

            /// Execute rest of code INDSIDE the dispatch code block INSTEAD of after it...
        });
     }];
}
0
votes

Try putting CFRunLoopWakeUp(CFRunLoopGetCurrent()) just below when you present the alert controller.