Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for another thread to complete before continuing with a segue?

I have two UITableViewControllers and I am segueing between them, using a storyboard.

In prepareForSegue:sender:, the first VC gets an NSArray from a web service and sets the model of the destination VC with the downloaded data. I put the downloading part into a dispatch_queue_t so it will run asynchronously and not block the UI thread. I want to tap a table cell, have the UIActivityIndicatorView start spinning, download the photos, and when the photos are downloaded, stop the spinner and continue with the segue.

In the first UITableViewController I have a prepareForSegue:sender: method:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"SelectPlace"]) {
        NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];

        UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]
                                            initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        [spinner startAnimating];
        self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:spinner];

        __block NSArray *photos = [[NSMutableArray alloc] init];
        dispatch_queue_t downloadQueue = dispatch_queue_create("flickr download", NULL);
        dispatch_async(downloadQueue, ^{
            photos = [FlickrFetcher photosInPlace:[self.places objectAtIndex:indexPath.row] maxResults:50];
        });
        dispatch_release(downloadQueue);
        [spinner stopAnimating];
        [segue.destinationViewController setPhotos:photos withTitle:[[sender textLabel] text]];
    }

}

Right now it is immediately performing the segue without showing the spinner or waiting for the download to complete.

How can I asynchronously download the data to prepare the destination view without blocking the main thread, but also without immediately segueing?

like image 844
jjeaton Avatar asked Oct 21 '12 18:10

jjeaton


2 Answers

Have your destination view controller load the data. When the user selects a row in your table, you should immediately segue to the new view controller. This is necessary to keep the user interface snappy. In prepareForSegue, give the destination view controller the data it will need to do the load itself. Then in the viewWillAppear in the destination view controller, load the data asynchronously.

If you want to keep the destination view controller generic and avoid doing the Flicker photo fetch from the destination view controller, you can set up a protocol that has a method such as getThePhotoData and have a dataSource pointer in your destination view controller that is set to self in prepareForSegue. Then, in viewWillAppear in the destination view controller, call [dataSource getThePhotoData] asynchronously. The flicker photo fetch will take place in the getThePhotoData method which will be implemented in the view controller that triggered the segue.

like image 180
vacawama Avatar answered Nov 19 '22 11:11

vacawama


In prepareForSegue you cannot stop segue. You must rather not trigger segue until you have all data. So I would suggest you to create manual segue and call it when you have all data downloaded.

For instance the chain of events can be similar to this one:

  1. In your VC register for notification (dataReady:) in viewDidLoad and unregister in dealloc,
  2. User clicks download button and fire your target method, you start spinner animation
  3. Target method downloads all data with some block (preferably in some model),
  4. At the end of the block you send notification ([[NSNotificationCenter defaultCenter] postNotificationName...),
  5. Your VC dataReady: is called and you stop spinner and call [self performSegueWithIdentifier:...]
  6. Segue is triggered
like image 44
Tomasz Zabłocki Avatar answered Nov 19 '22 11:11

Tomasz Zabłocki