0

I have a button in a cell which calls a protocol that has data that needs to be passed to the view controller by the segue. The segue is happening through storyboard. My current code uses the shouldperformsegue to return no when the button is pressed as the first segue that happens does not have the data. Im guessing the segue and the protocol are being handled asynchronously.

But before I return NO I tell it to perform the segue at a delay. This delayed segue does have the data and works fine.

My question is there a way to wait for the protocol to finish and then perform the segue? Maybe with an execution block?

3 Answers3

1

The other responders have hinted about this, but haven't stated it explicitly, so here goes.

Do not tie a segue directly to the button. Instead, control-drag from the source view controller SCENE to the destination scene to create a segue at the view controller level. Give the segue a unique identifier.

Then, in your button IBAction code, do the async network download. You may want to display a "loading, please wait" message or something while the download is taking place. Most async network calls take a completion block. In that completion block, wrap a call to performSegueWithIdentifier in a call to dispatch_async(dispatch_get_main_queue() so the segue gets invoked on the main thread. (@SantaClaus's answer shows the syntax for that.)

So your button IBAction code might look like this:

- (IBAction) buttonAction: (UIButton *) sender;
{
  //Display a "please wait"message to the user if desired
  doAsyncCallTakingBlock( completion: ^(NSData *data)
  {
    //parse the data, (or whatever)

    dispatch_async(dispatch_get_main_queue(), ^
    {
      //This call uses the button as the sender. That might be appropriate,
      //or not. 
      [self performSegueWithIdentifier:@"jumpToOtherViewController" 
        sender: sender];
    });
  }
}

With this approach the segue doesn't get called until the async method (Which I called doAsyncCallTakingBlock in my example code) has finished it's work. You might call an Alamofire request method, use an NSURLSession, or any other async method that takes a completion block.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I wouldn't have minded that you stole my `dispatch_async` block if it wasn't for your ugly curly bracket style... :P – Andrew Jun 30 '16 at 00:31
  • No, I fixed your ugly-a--d formatting. :) I am an [**Allman man**](https://en.wikipedia.org/wiki/Indent_style#Allman_style) , through and through. – Duncan C Jun 30 '16 at 00:42
  • @SantaClaus, I gave you credit for being the first to post the dispatch_async code, nasty formatting notwithstanding. :) – Duncan C Jun 30 '16 at 00:44
0

Check out performSegueWithIdentifier on UIViewController. If you set up a segue between the view controllers in your storyboard (not from the button) and give it an identifier, you can perform the segue as soon as the data is ready.

Since you mentioned that your data is being fetch asynchronously, you might need to dispatch the performSegueWithIdentifier call to the main thread like so:

dispatch_async(dispatch_get_main_queue(), ^{
  [self performSegueWithIdentifier:@"jumpToOtherViewController" sender:self];
});

To actually pass the data on to the next view controller, you can use prepareForSegue as described here.

Community
  • 1
  • 1
Andrew
  • 15,357
  • 6
  • 66
  • 101
  • what about the segue that happens when i click the button? Do I simply just cancel it? – city parking Jun 29 '16 at 17:01
  • @cityparking Delete that segue. You will not be using it anymore. Instead, link the button to an `@IBAction` which starts the data fetch. When the data fetch finishes, call `performSegueWithIdentifier` as shown. – Andrew Jun 29 '16 at 17:06
  • @cityparking Also, I just changed the code to Objective-C. Sorry about that. – Andrew Jun 29 '16 at 17:08
0

Yes, I would use blocks. For example:

[Api getDataWithCompletion:^(BOOL success, NSString *data) {
    if (success) {
        self.data = data;
        [self performSegueWithIdentifier:@"MySegue" sender:self];
    } else {
        NSLog(@"Failed to get data");
    }
}];

Then, to pass it to the next view controller:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"MySegue"]) {
        TargetViewController *targetVC = (TargetViewController*)segue.destinationViewController;
        targetVC.data = self.data;
    }
}
John Farkerson
  • 2,543
  • 2
  • 23
  • 33