It's a communications issue between view-controllers resulting out of an unclear parent-child view-controller relationship... Without using a protocol and delegation, this won't work properly.
The rule of thumb is:
- Parents know about their children, but children don't need to know about their parents.
(Sounds heartless, but it makes sense, if you think about it).
Translated to ViewController relationships: Presenting view controllers need to know about their child view controllers, but child view controllers must not know about their parent (presenting) view controllers: child view controllers use their delegates to send messages back to their (unknown) parents.
You know that something is wrong if you have to add @Class declarations in your headers to fix chained #import compiler warnings. Cross-references are always a bad thing (btw, that's also the reason why delegates should always be (assign) and never (strong), as this would result in a cross-reference-loop and a group of Zombies)
So, let's look at these relationships for your project:
As you didn't say, I assume the calling controller is named MainController. So we'll have:
- A MainController, the parent, owning and presenting the InfoController
- An InfoController (revealed partially below MainController), owning and presenting a:
- MailComposer, which cannot be presented because it would be displayed below the MainController.
So you want to have this:
- A MainController, the parent, owning and presenting the InfoController & MFMailController
- An InfoController (revealed partially below MainController)
- an "Email-Button" in the InfoController's view. On click it will inform the MainController (it's unknown delegate) that it should dismiss the InfoController (itself) and present the MailComposer
- an MailComposer that will be owned (presented & dismissed) by the MainController and not by the InfoController
1. InfoController: Defines a @protocol InfoControllerDelegate:
The child controller defines a protocol and has a delegate of unspecified type which complies to its protocol (in other words: the delegate can be any object, but it must have this one method)
@protocol InfoControllerDelegate
- (void)returnAndSendMail;
@end
@interface InfoControllerDelegate : UIViewController // …
@property (assign) id<InfoControllerDelegate> delegate
// ...
@end
2. MainController owns and creates both InfoController and MFMailController
...and the MainController adopts both the InfoControllerDelegate and the MFMailComposeDelegate protocol, so it can dismiss the MFMailComposer again (Note, that doesn't and probably shouldn't need to be strong properties, just showing this here to make it clear)
@interface MainController <InfoControllerDelegate, MFMailComposeViewControllerDelegate>
@property (strong) InfoController *infoController;
@property (strong) MFMailComposeViewController *mailComposer;
3. MainController presents its InfoViewController and sets itself as the delegate
// however you get the reference to InfoController, just assuming it's there
infoController.delegate = self;
[self presentViewController:infoController animated:YES completion:nil];
The 'infoController.delegate = self' is the crucial step. This gives the infoController a possibility to send a message back to the MainController without knowing it ObjectType (Class). No #import required. All it knows, it that it's an object that has the method -returnAndSendMail; and that's all we need to know.
Typically you would create your viewController with alloc/init and let it load its xib lazily.
Or, if you're working with Storyboards and Segues, you probably want to intercept the segue (in MainController) in order to set the delegate programmatically:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// hook in the segue to set the delegate of the target
if([segue.identifier isEqualToString:@"infoControllerSegue"]) {
InfoController *infoController = (InfoController*)segue.destinationViewController;
infoController.delegate = self;
}
}
4. In InfoController, the eMail button is pressed:
When the eMail button is pressed, the delegate (MainController) is called. Note that it's not relevant that self.delegate is the MainController, it's just relevant that it has this method -returnAndSendMail
- (IBAction)sendEmailButtonPressed:(id)sender {
// this method dismisses ourself and sends an eMail
[self.delegate returnAndSendMail];
}
...and here (in MainController!), you'll dismiss the InfoController (clean up because it's the responsibility of the MainController) and present the MFMailController:
- (void)returnAndSendMail {
// dismiss the InfoController (close revealing page)
[self dismissViewControllerAnimated:YES completion:^{
// and present MFMailController from MainController
self.mailComposer.delegate = self;
[self presentViewController:self.mailComposer animated:YES completion:nil];
}];
}
so, what you're doing with the MFMailController is practically the same as with the InfoController. Both have their unknown delegate, so they can message back and if they do, you can dismiss them and proceed with whatever you should to do.
Notes
- -dismissViewControllerAnimated:completion: should not be called from the child view controller. In the docs, it says: "The presenting view controller is responsible for dismissing the view controller it presented.". That's why we still need delegation. And it's useful, because the relationships and responsibilities of parents are important! Indeed. You can't create something and then just leave it be. Well, you can, but you shouldn't.
- if you wouldn't use a revealing view controller animation, you could chain these Parent (adopting Child Protocol) - Child (defining protocol for parent and adopting protocol for grandchild) - Grandchild (defining protocol for ...
- Again: a design where one MainController is owning and presenting all the child viewController is really a bad design. So the solution presented is about protocols and communication and not about putting everything in one MainController
- I don't think that blocks as a coding technology free us from the need to define relationships and declare Protocols
- Hope that helps