2
votes

I have a TableViewController with a button that triggers an [NSURLConnection sendAsynchronousRequest...] event, and also loads a modal segue through performSegueWithIdentifier:sender: which targets a small view controller. The purpose of this overlay view controller is to show a loading graphic and to prevent user interaction while the data is sent through the NSURLConnection.

In the completion block of the NSURLConnection, I call a method which removes the data in the TableViewController (its just a batch listing), and then calls dismissViewControllerAnimated:completion: on the overlay view controller.

Everything works except for dismissing the overlay view controller, which throws a warning in the debugger which says: "Warning: Attempt to dismiss from view controller while a presentation or dismiss is in progress!"

I have found various questions and answers about this error, particularly about using the performSelector:object:withDelay methods, but so far nothing has worked.

This is particularly annoying because I use a similar process in another area of the app, except that the dismissViewController is called from selecting a UITableViewCell, and this works fine...

The relevant bits of my code are shown below:

#import "ViewBatch.h"

@interface ViewBatch ()
@property (strong, nonatomic) LoadingOverlayViewController *loadingOverlay;
@end

@implementation ViewBatch
@synthesize loadingOverlay;

....

- (IBAction)exportBatch:(id)sender
{
    if ([productArray count] > 0) {
        [self performSegueWithIdentifier:@"loadingSegue" sender:self];
        [self processData];
    }
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"loadingSegue"]) {

        loadingOverlay = segue.destinationViewController;
    }
}

- (void)processData
{

    // Code to create a file and NSURLRequest...
    // ....

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:urlRequest
                                       queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
                               if ([responseData length] > 0 && error == nil)
                               {
                                   // Not used for this request yet...
                               }
                               else if ([responseData length] == 0 && error == nil)
                               {
                                   // Success...
                                   [self didSendData];
                               }
                               else if (error != nil)
                               {
                                   // Connection error...
                                   NSLog(@"Error: %@", error);
                               }
                          }];
}

- (void)didSendData
{
    // Reset the batch...

    [productArray removeAllObjects];
    [self.tableView reloadData];

    [loadingOverlay dismissViewControllerAnimated:YES completion:NULL];
}

And the loading view controller is just:

#import <UIKit/UIKit.h>

@interface LoadingOverlayViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;

@end

....

....

#import "LoadingOverlayViewController.h"

@interface LoadingOverlayViewController ()
@end

@implementation LoadingOverlayViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.activityIndicator startAnimating];
}

@end
3

3 Answers

1
votes

I'm not sure about the exact cause of the problem, but here are some observations about your code.

  • You do not need an overlay in order to prevent user interaction. Simply turn off user interaction with -NSApplication beginIgnoringInteractionEvents.

  • You do not need an entire view controller in order to show an overlay view. Just show the view. For example I often place a large UIActivityIndicatorView in the middle of my view while an NSURLConnection is happening.

  • You do not need an NSOperationQueue in order to use NSURLConnection asynchronously. It is already asynchronous. Just create the NSURLConnection and wait for the delegate messages to arrive. As it is, you are bolluxing yourself because you are setting up a secondary queue, the message arrives on that queue, and you call didSendData which calls reloadData on the table - in the background, which is illegal. If you did this the normal way, your delegate messages would arrive on the main thread which is exactly what you want.

0
votes

Never mind, I got it worked out. Amazing what half an hours break can do to organise one's thoughts...

The process was finishing and calling dismissViewController before the actual view was even finished loading. A simple delegate call from the viewDidLoad on the overlay sorted things out.

0
votes

I had this problem as well. I noticed it was a result from copying and pasting a button in my nib file. The result of this was that I had to IBActions tied two 1 UIButton.