iPhone MailComposer class UIViewController dismissModalViewControllerAnimated issues - iphone

I created a class to launch the MailComposer so that my iPhone app would only have one place to go when generating various kinds of e-mail: some with attachments, some not. Some with pre-filled addresses, some not.
I didn't want my class implement UIViewController, but it has to so it can be the delegate for the MailComposer. Otherwise, the view controllers that call my class would themselves have to be delegates for the MailComposer, which defeats the purpose.
The downside of having my class be a view controller is that it has to load to the screen before it can modally bring up the MailComposer. Unfortunately, view controllers can't be transparent. The effect is, whatever is on screen gets covered by a solid white view controller for a moment before the MailComposer appears.
I could maybe live with that, but not this: after the MailComposer goes away, I'm left with my blank view controller occupying the screen. I ought to be able to get rid of it from within itself by calling this:
[self.parentViewController dismissModalViewControllerAnimated:NO];
But that dies a horrible death: "Loading 43365 stack frames..."
Has my class -- a UIViewController that pre-fills and then launches a MailComposer -- lost track of its parentViewController? It isn't nil, because I've tested for that.
As launched from within the current view controller...
// My class is called Email.
Email *oEmail = [[[Email alloc] init] retain];
// Red, to remind myself that I'd like to someday learn to make it transparent.
oEmail.view.backgroundColor = [UIColor redColor];
// Pre-fill whatever fields you want, and specify attachments.
oEmail.EmailSubject = #"I am truly stumped";
// This has to go on screen first.
[self presentModalViewController:oEmail animated:NO];
// Then this can happen, which brings up the MailComposer.
[oEmail f_SendEmail];
// Commenting out the next line didn't help, so I turned it back on.
[oEmail release];
Inside the class, you need the mailComposeController:didFinishWithResult:error: method to make the MailComposer go away, and for that to happen, the class has to be the MFMailComposeViewControllerDelegate. Here's what happens in there:
// This gets rid of the mail composer.
[self dismissModalViewControllerAnimated:YES];
// This never fails to get rid of other modal view controllers when called
// from within those controllers, but boy does it not work here.
[self.parentViewController dismissModalViewControllerAnimated:NO];
If you can help me, I will be truly thankful!

Instead of calling
[self.parentViewController dismissModalViewControllerAnimated:NO];
I would set up a delegate for your 'Email' controller.
An example of this sort of connection can be seen in the 'FlipSide' application template when creating a new project.
Basically, you would set up a delegate for the Email controller:
Email *oEmail = [[[Email alloc] init] retain];
oEmail.view.backgroundColor = [UIColor redColor];
oEmail.EmailSubject = #"I am truly stumped";
[self presentModalViewController:oEmail animated:NO];
[oEmail f_SendEmail];
[oEmail setDelegate:self];
[oEmail release];
Then in the Email .h file:
#protocol EmailDelegate
-(void)emailDidFinish;
#end
#implementation Email : UIViewController {
// Other stuff
id <EmailDelegate> delegate;
}
#property (nonatomic, assign) id <EmailDelegate> delegate;
#end
Make sure you #synthesize delegate, then when you're ready to dismiss it call:
// This gets rid of the mail composer.
[self dismissModalViewControllerAnimated:YES];
// This never fails to get rid of other modal view controllers when called
// from within those controllers, but boy does it not work here.
if (delegate && [delegate respondsToSelector:#selector(emailDidFinish)]){
[delegate emailDidFinish];
}
And finally, in your original view controller, make sure you've got in the .h file and then have:
-(void)emailDidFinish {
[self dismissModal...];
}
Hope that helps.

I had the same problem and I solved it a different way.
I created a function which pops current ViewController.
In the h:
-(void)ics;
In the cpp:
-(void)ics{
//[self.navigationController popViewControllerAnimated:NO];
[self.navigationController popToRootViewControllerAnimated:YES];
}
and called it after dismissing the MailComposer:
[self dismissModalViewControllerAnimated:YES];
[self ics];
voila!

Related

Warning: Attempt to present ViewController on ViewController whose view is not in the window hierarchy

I have already looked through related questions but nothing has solved my problem.
I am attempting to use dismissViewControllerAnimated:animated:completion and presentViewControllerAnimated:animated:completion in succession. Using a storyboard, I am modally presenting InfoController via a partial curl animation.
The partial curl reveals a button on InfoController that I want to initiate the MFMailComposeViewController. Because the partial curl partially hides the MFMailComposeViewController, I first want to dismiss InfoController by un-animating the partial curl. Then I want the MFMailComposeViewController to animate in.
At present, when I try this, the partial curl un-animates, but the MFMailComposeViewController doesn't get presented. I also have a warning of:
Warning: Attempt to present MFMailComposeViewController: on
InfoController: whose view is not in the window hierarchy!
InfoController.h:
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>
#interface InfoController : UIViewController <MFMailComposeViewControllerDelegate>
#property (weak, nonatomic) IBOutlet UIButton *emailMeButton;
-(IBAction)emailMe:(id)sender;
#end
InfoController.m
#import "InfoController.h"
#interface InfoController ()
#end
#implementation InfoController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)emailMe:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
[self sendMeMail];
}];
}
- (void)sendMeMail {
MFMailComposeViewController *mailController = [[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
if(mailController)
{
NSLog(#"%#", self); // This returns InfoController
mailController.mailComposeDelegate = self;
[mailController setSubject:#"I have an issue"];
[mailController setMessageBody:#"My issue is ...." isHTML:YES];
[self presentViewController:mailController animated:YES completion:nil];
}
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(#"It's sent!");
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Also, if I comment out the [self dismissViewControllerAnimated:YES completion:^{}]; in (IBAction)emailMe, the MFMailComposeViewController animates in but it is partially hidden behind the partial curl. How can I first dismiss the curl and then animate in the MFMailComposeViewController?
Thanks very much!
Edit: Below image of what the view looks like if I comment out [self dismissViewControllerAnimated:YES completion:^{}];
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
When you present a viewController, the viewController you are presenting from needs to be in the view hierarchy. The two VC's hold pointers to each other in their properties presentingViewController and presentedViewController, so both controllers need to be in memory.
By dismissing then running presenting code from the being-dismissed view controller, you are breaking this relationship.
Instead, you should be doing the presenting of your mailController from the viewController that presented InfoController, after the infoController has been dismissed.
The trad way to do this is via a delegate callback to the underlying viewController which then handles the two steps of dismissing and presenting. But now we use blocks..
Move your sendMeMail method into the VC that presented infoController
Then - in infoController - you can call it in the completion block…
- (IBAction)emailMe:(id)sender {
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
if ([presentingVC respondsToSelector:#selector(sendMeMail)])
[presentingVC performSelector:#selector(sendMeMail)
withObject:nil];
}];
}
(you need to get a local pointer to self.presentingViewController because you cannot refer to that property after the controller has been dismissed)
Alternatively keep all of the code in infoController by putting the sendMeMail code in the completion block:
- (IBAction)emailMe:(id)sender {
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
MFMailComposeViewController *mailController =
[[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
if(mailController) {
NSLog(#"%#", self); // This returns InfoController
mailController.mailComposeDelegate = presentingVC; //edited
[mailController setSubject:#"I have an issue"];
[mailController setMessageBody:#"My issue is ...." isHTML:YES];
[presentingVC presentViewController:mailController
animated:YES
completion:nil];
}
}
}];
}
update/edit
If you put all of the code in the completion block, you should set mailController's mailComposeDelegate to presentingVC, not self. Then handle the delegate method in the presenting viewController.
update 2
#Auro has provided a detailed solution using a delegate method, and in his comments points out that this best expresses the separation of roles. The traditionalist in me agrees, and I do regard dismissViewController:animated:completion as a kludgy and easily misunderstood piece of API.
Apple's docs have this to say:
Dismissing a Presented View Controller
When it comes time to dismiss a presented view controller, the preferred approach is to let the presenting view controller dismiss it. In other words, whenever possible, the same view controller that presented the view controller should also take responsibility for dismissing it. Although there are several techniques for notifying the presenting view controller that its presented view controller should be dismissed, the preferred technique is delegation. For more information, see “Using Delegation to Communicate with Other Controllers.”
Notice that they don't even mention dismissViewController:animated:completion: here, as if they don't have much respect for their own API.
But delegation seems to be an issue that people often struggle with: and can require a lengthy answer... I think this is one of the reasons Apple pushes blocks so hard. In cases where code only needs to execute in one place, the delegate pattern is often regarded by the uninitiated as an overly complex solution to an apparently simple problem.
I suppose the best answer, if you are learning this stuff, is to implement it both ways. Then you will really get a grip on the design patterns in play.
About this,
- (IBAction)emailMe:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
[self sendMeMail];
}];
}
After dismissing self viewController, you cannot present view controllers from self.
Then what you can do ?
1) Change the button press method,
- (IBAction)emailMe:(id)sender {
[self sendMeMail];
}
2) You can dismiss the self viewController, when the mailViewController is dismissed.
- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error;
{
if (result == MFMailComposeResultSent) {
NSLog(#"It's sent!");
}
[controller dismissViewControllerAnimated:NO completion:^ {
[self dismissViewControllerAnimated:YES completion:nil];
}];
}
If you're using storyboards, try checking the transition types you're using on your segues. You will have issues dismissing layers of viewcontrollers you transition to modally. This may be the source of your problems. Try switching them to push instead. Although you can write delegates to accomplish dismissing viewcontrollers it really shouldn't be needed. This is an overly complicated solution. Image if you have a viewcontroller that transitions to dozens of different storyboards, are you going to have dozens of delegates controlling dismissal? It seems suboptimal.
Use this code
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:composer animated:YES completion:nil];
Instead of
[self presentViewController:picker animated:YES completion:NULL];

Attempt to dismiss from view controller while a presentation or dismiss is in progress

I have TWO UIViewController classes, where in FirstClass i have an UIButton for Login, when user taps on button, i will display SecondClass... For that i have done,
SecondClass *index = [[SecondClass alloc] init];
[self presentModalViewController:index animated:YES];
In SecondClass i have a logout button, which will redirect to FirstClass, for that i have done,
[self dismissModalViewControllerAnimated:YES];
When i press Logout button in SecondClass, i get the warning msg
**Attempt to dismiss from view controller <FirstClass: 0e39w88e160> while a presentation or dismiss is in progress!**
What is the problem here..
Added both iOS 6 and pre-iOS 6 answers:
iOS 5.0 and later
When you logout, add this check before dismissing:
if (![self.presentedViewController isBeingDismissed])
{
[self dismissModalViewControllerAnimated:YES completion:nil];
}
iOS 4.X and less
Add this check before dismissing:
if (![[self modalViewController] isBeingDismissed])
{
[self dismissModalViewControllerAnimated:YES];
}
Call these lines where you logout & then check:
if (![[self modalViewController] isBeingDismissed])
{
[self dismissModalViewControllerAnimated:YES];
}
There are many things that may cause this, here are some options:
You forgot to call super on one of the ViewController methods such as viewWillAppear, viewWillAppear etc. Consult the UIViewController documentation to see when you have to call super.
The dismissModalViewControllerAnimated: method is being called more than once, this can happen if you added a target to the UIButton more than once.
To get better understanding of the problem please paste the code of both view controllers in its entirety.

Unloading a View in Objective-C (iPhone)

I have a View-based app. The first view that is loaded has a button which loads another view using this code:
AddPost *addView = [[AddPost alloc] initWithNibName:#"AddPost" bundle:nil];
addView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:addView animated:YES];
What I want to do is provide a button on the view (AddPost) that will let me close it and go back to the original view. How can I do this?
It seems a little strange, but you can actually have addView call:
[self dismissModalViewControllerAnimated:YES];
From the docs:
"The parent view controller is responsible for dismissing the modal view controller it presented using the presentModalViewController:animated: method. If you call this method on the modal view controller itself, however, the modal view controller automatically forwards the message to its parent view controller."
The answer Conrad gave will work perfectly well. In the name of slightly better encapsulation you could put a delegate protocol on addView and have your first view implement this.
So in the header file for your addView controller:
#protocol addViewDelegate <NSObject>
- (void)addViewRequestDismissal;
#end
You will also need an external properly on the addView controller:
#property (assign) id<addViewDelegate> delegate;
Then make your first view controller implement this, so in it's .h file you should have
#interface firstView : NSObject <addViewDelegate> {
}
When you instantiate your addView remember to set the delegate:
addView.delegate = self;
In the addView controller when your button is pressed call back:
- (void)buttonPressed {
[self.delegate addViewRequestDismissal];
}
Finally in your first view remember to implement this method
- (void)addViewRequestDismissal {
[self dismissModalViewControllerAnimated:YES];
}
Hope all goes well with this. Post back if you have any further problems :)

Navigating between several views in iphone

Hi I have three views and I would like to achieve something that doesn't work. I have a main view if user presses a certain button the code checks if he is logged or not:
if yes he is sent directly to view B if not first he goes to login view.
After successfull login I have this code to go to view b:
incidencias =[[MisIncidencias alloc]
initWithNibName:#"MisIncidencias"
bundle:nil];
[self.view addSubview:incidencias.view];
the thing is I would like to get rid of the login view because it shows there underneath plus if user clicks back it goes back to login but if I add:
[self.view removeFromSuperview];
either before or after [self.view addSubview:incidencias.view], I just get redirected to the main view;
I don't know if I explained myself clearly but for example in Android you can just call finish and then call next activity and the login activity disappears but here in iphone I don't know what to do.
I have found another solution is to add both views one after another but it doesn't really work well:
incidencias=[[MisIncidencias alloc]
initWithNibName:#"MisIncidencias"
bundle:nil
];
[self.view addSubview:incidencias.view];
login=[[LoginViewController alloc]
initWithNibName:#"LoginViewController"
bundle:nil];
[self.view addSubview:login.view];
it doesn't work well because incidencias starts and doesn't wait for login to finish.
thanks
EDIT: thanks to beOn I have modified my code adding the protocol:
LoginViewControllerDelegate
and this method inside viewController:
- (void)loginSucceededFromController:(LoginViewController*)viewController {
[viewController.view removeFromSuperview];
incidencias =[[MisIncidencias alloc]
initWithNibName:#"MisIncidencias"
bundle:nil];
[self.view addSubview:incidencias.view];
}
in LoginViewController I have
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex: (NSInteger)buttonIndex{
if(self.delegate)
[self.delegate loginSucceededFromController:self]
}
it gets an error:
Semantic Issue: Property 'delegate' not found on object of type 'LoginViewController *'
if login is successful the user sees an alert and once he clicks on ok is when the method above gets called.
what else should I add? I am beginning with iphone and I don't understand very well what is delegate (I come from java)
Ah, okay, this ain't so bad. Here's the first solution that comes to mind:
Step 1. Create a delegate protocol for your login view.
#protocol LoginViewControllerDelegate <NSObject>
#required
- (void)loginSucceededFromController:(LoginViewController*)viewController;
#end
Step 2. Implement the protocol in your main view controller
- (void)loginSucceededFromController:(LoginViewController*)viewController {
// TODO: we'll put something here in a second
}
Step 3. Call the delegate method from your login view on successful login
if (loginSuccess && self.delegate) {
[self.delegate loginSucceededFromController:self]
}
Step 4. Dismiss the login view and present the new view from the main view controller using the code you already have:
- (void)loginSucceededFromController:(LoginViewController*)viewController {
[viewController.view removeFromSuperview];
incidencias =[[MisIncidencias alloc]
initWithNibName:#"MisIncidencias"
bundle:nil];
[self.view addSubview:incidencias.view];
}
Hopefully that clears things up some. The reason you were having trouble is that you were either adding a subview to a view, then immediately removing the view, or removing the view, then adding a subview to it. In the code above, you call the view's controller's delegate, and the delegate, which happens to own the superview of the view, first removes the view, then adds a newView (for lack of a better term) to the superview. Since the superview was never removed, it's able to show your newView.
You have to take BOOL which one can access through out application like global
like extern BOOL login; now once you login set to YES. now check when
if(login == YES){
incidencias=[[MisIncidencias alloc]
initWithNibName:#"MisIncidencias"
bundle:nil
];
[self.view addSubview:incidencias.view];
}
else{
login=[[LoginViewController alloc]
initWithNibName:#"LoginViewController"
bundle:nil];
[self.view addSubview:login.view];
}
If you want something working right away, and you are using uinavigationcontroller... then u can possibly make use of
- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated
just get a mutable copy of the self.navigationController.viewcontrollers array, pop out the last element, which will be the login screen and push in the new screen where you are planning to move screen b.. and pass the array to this function.. and you are now safe!

ipad - dismissing a UIPopoverController

I have a button inside the content of a UIPopoverController. This button runs a method called myAction.
MyAction has the form
- (void) myAction:(id)sender
so, myAction receives the id of the caller button.
Now, inside this method I would like to dismiss the UIPopoverController, but the only thing I have is the ID of the caller button. Remember that the button is inside the UIPopoverController.
Is there a way to discover the ID of the UIPopoverController, given the button ID I already have?
thanks.
Unfortunately no. At least, not within the standard practices. You might be able to travel up the responder stack to find it, but it's a hack, it's buggy, and it's really, really messy.
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover. Usually that would be the owner of the popover (not the controller showed within the popover). When the button is pressed, it can send a message to the owner controller, which can then dismiss the popover.
You might be tempted to have the controller displayed inside of the popover be the owner of its own popover, but coding this way is brittle, can get messy (again), and may result in retain loops so that neither ever gets released.
You can access the presenting popoverController by accessing "popoverController" with KVC.
[[self valueForKey:#"popoverController"] dismissPopoverAnimated:YES]
I have this working, and I do not think it is a hack. I have a standard split view iPad app. I then added a method on my detail controller (the owner of the pop over) to handle the dismissal.
On the standard split view architechture, both the root and detail view controllers are available via the app delegate. So I bound a button click inside the pop over to call a method which gets the app delegate. From there I call the method on the detail controller to dismiss the pop over.
This is the code for the method on the View Controller that is displayed inside the popover:
- (void) exitView: (id)sender {
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.detailViewController exitDrill];
}
Then the simple method to dismiss on the Detail View Controller:
- (void) exitDrill {
if(dtController != nil){
[dtController dismissPopoverAnimated: YES];
[dtController release];
}
}
I like the ability to do this because it give me a way to show a user how they can exit a pop over. This may not be necessary in future versions of the app; for right now, while this paradigm is still new to the platform, I prefer to let the users gexit a display in a couple fo different ways to make sure I minimize frustration.
As Ed Marty already wrote
If you want to dismiss a popover by pushing a button, some place relevant should keep a reference to the popover
This is very true; however, when showing a UIPopoverController, the class opening the popovercontroller keeps this resource already. So, what you could do is to use this class as the delegate class for your Popover Controller.
To do so, you could do the following, which I use in my code.
In the class opening the popover, this is my code:
- (void)showInformationForView:(Booking*)booking frame:(CGRect)rect
{
BookingDetailsViewController *bookingView = [[BookingDetailsViewController alloc] initWithStyle:UITableViewStyleGrouped booking:booking];
[bookingView setDelegate:self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookingView];
self.popController = [[UIPopoverController alloc] initWithContentViewController:navController];
[self.popController setDelegate:self];
[self.popController setPopoverContentSize:CGSizeMake(320, 320)];
rect.size.width = 0;
[self.popController presentPopoverFromRect:rect inView:self.view permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
}
- (void)dismissPopoverAnimated:(BOOL)animated
{
[self.popController dismissPopoverAnimated:animated];
}
So what I am doing here is creating a UINavigationController and setting a BookingDetailsViewController as its rootViewController. Then I am also adding the current class as delegate to this BookingDetailsViewController.
The second thing I added is a dismissal method called dismissPopoverAnimated:animated.
In my BookingDetailsViewController.h I added the following code:
[...]
#property (nonatomic, strong) id delegate;
[...]
And in my BookingDetailsViewController.m I added this code:
[...]
#synthesize delegate = _delegate;
- (void)viewDidLoad
{
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(closeView)];
[self.navigationItem setRightBarButtonItem:closeButton];
[super viewDidLoad];
}
- (void)closeView
{
if ([self.delegate respondsToSelector:#selector(dismissPopoverAnimated:)]) {
[self.delegate dismissPopoverAnimated:YES];
}
else {
NSLog(#"Cannot close the view, nu such dismiss method");
}
}
[...]
What happens is that when the "Close" button in the UINavigationController is pressed, the method closeView is called. This method check if the delegate responds to dismissPopoverAnimated:animated and if so, it calls it. If it does not respond to this method it will show a log message and do nothing more (so it wont crash).
I have written my code using ARC, hence there is no memory management.
I hope this helped you.