2

I've spent a few hours on this trying to work it out myself but I give up!

I have a master-detail arrangement where the user input screen needs to call a function on another class to post to a web service. Upon completion of the asynchronous call, the class will then call a specified function. In this case, I'm just testing and all I want to do is go back to the main screen after the user input is accepted by the web service.

When the uses taps a button on the input screen (SetLocationViewController), the asynchronous operation is called in the class APIPostClass. After it is complete, I want SetLocationViewController to segue back to MasterViewController.

In APIPostClass.m in (called after the asynchronous op finishes)

-(void)callWhenDone {
NSLog(@"callWhenDone loaded.");

SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}

In SetLocationViewController.m

-(void) doSegue {
NSLog(@"doSegue loaded");
[self performSegueWithIdentifier:@"SetLocationViewControllerManualUnwind" sender:self];
}

Calling doSegue from an action on SetLocationViewController.m does work so I know my segue is ok but the above doesn't work. I get the error reason: 'Receiver () has no segue with identifier 'SetLocationViewControllerManualUnwind''

I'm guessing the reason is because of the alloc init way of initialising of the VC, but I don't know any better. Thus, how can I call a function on another class as if it was being called by it's own class?

Warren
  • 1,984
  • 3
  • 29
  • 60
  • Are you trying to segue back to the first viewController? if so, you can use [self.navigationController popViewControllerAnimated:YES]; at the end of your callback (asynch delegate) when your background job is done. – Alice Jun 18 '14 at 10:16
  • Just FTR it's possible this may help you greatly: http://stackoverflow.com/questions/23399061 especially the "critical tip" at the end. You can often completely resolve these confusing issues by just passing the "your boss" instance around. – Fattie Jun 22 '14 at 08:35

3 Answers3

3

Create a delegate it would be much more reliable and fast than Notifications.

@protocol APIPostDelegate <NSObject>
@required
-(void)OnRequestSucess;
@end

In your APIPost add new property for delegate

@interface APIPost : NSObject

@property (weak) id<APIPostDelegate> delegate;

In SetLocationViewController implement APIPostDelegate

SetLocationViewController.h

SetLocationViewController :NSObject<APIPostDelegate>

SetLocationViewController.m

-(void)OnRequestSucess
{
   [self doSegue];
}

before you make call to method on APIPost, assign self to delegate property.

APIPost *apipost=[[APIPost alloc]init];
apipost.delegate=self;

[apipost <your api method>];

APIPost.m

[self.delegate OnRequestSucess];

Hope this helps.

Balpreet Patil
  • 1,644
  • 2
  • 16
  • 16
  • Thanks Balpreet. Just a couple of things for anyone in future: when you refer to "ApiDelegate", I think you mean "APIPostDelegate" and in your last line, instead of "apipost " I had to use "self.delegate – Warren Jun 19 '14 at 08:51
  • Thanks Warren for spotting missing/incorrect bits. I have updated the answer. – Balpreet Patil Jun 19 '14 at 09:18
0

There are a few methods to make it happens:-

  1. Use Delegate
  2. Use NSNotification.
  3. The way described by Artur above (For SplitViewController Only - iPad)

You should use delegate whenever it is possible but it might not be too straight forward. NSNotification is more straight forward but it is not a good practice and not a good programming style.

I will only share the NSNotification method as it is easier to implement.

In SetLocationViewController.m

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSegue) name:@"calldoSegue" object:nil];
}

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter]removeObserver:self name:@"calldoSegue" object:nil];
}

-(void) doSegue {
    NSLog(@"doSegue loaded");
    [self performSegueWithIdentifier:@"SetLocationViewControllerManualUnwind" sender:self];
}

In APIPostClass.m

-(void)callWhenDone {
    NSLog(@"callWhenDone loaded.");
    [[NSNotificationCenter defaultCenter]postNotificationName:@"calldoSegue" object:nil];
}

The above code should work but again, this is not a good practice. You should try to learn the Delegate method.

Ricky
  • 10,485
  • 6
  • 36
  • 49
0

The answer is here: Performing segue from another class

In my APIPostClass.h, I setup the view controller:

@interface APIPostClass : NSObject  {
    SetLocationViewController *setLocationViewController;
}

@property(nonatomic, strong) SetLocationViewController *setLocationViewController;

@end

In my APIPostClass.m, I synthesize it:

@synthesize setLocationViewController;

then, instead of this (as in my question):

-(void)callWhenDone {
NSLog(@"callWhenDone loaded.");

SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}

I have:

-(void)callWhenDone {
    NSLog(@"callWhenDone loaded");
    [self.setLocationViewController doSegue];
}

Over in SetLocationViewController.m, the segue method remains unchanged:

-(void) doSegue {
NSLog(@"doSegue loaded");
[self performSegueWithIdentifier:@"SetLocationViewControllerManualUnwind" sender:self];
}

But when I call my API, I need to "attach" (forgive my terminology) the view controller to it. This is what I had:

- (IBAction)btnTestAPICall:(id)sender {
    NSLog(@"User tapped API button");

    APIPostClass *APIPostClassInstance = [[APIPostClass alloc] init];
    [APIPostClassInstance APICall: ... ....
}

But this is what works after bringing all of the above:

- (IBAction)btnTestAPICall:(id)sender {
    NSLog(@"User tapped API button");

    APIPostClass *APIPostClassInstance= [[APIPostClass alloc] init];

    UIViewController *currentVC=self;
    APIPostClassInstance.setLocationViewController = currentVC;

    [APIPostClassInstance APICall: ... ...

I hope this will help someone else!

Community
  • 1
  • 1
Warren
  • 1,984
  • 3
  • 29
  • 60
  • 1
    Hi Warren, Your solution surely works, but it is not best way to handle it. By having a property of type LocationViewController in your APIPostClass it is breaching abstraction. If there will be multiple consumers of APIPostClass you don't expect be adding property for each of them. How will you handle when caller it not LocationViewController? If you use delegate then as long as caller implement APIPost's delegate, it will work. – Balpreet Patil Jun 18 '14 at 14:29