ipad - dismissing a UIPopoverController - iphone

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.

Related

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!

How can I hide a button in a different view controller?

I am using this code to hide a button in a different view controller, but the button does not get hidden when the button is pressed to hide the button in the other view controller.
This is the code I am using to hide the button in the other view controller:
[self dismissModalViewControllerAnimated:YES];
NSLog(#"Exited");
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[vc.mainbutton1 setHidden:YES];
Why is this not working?
Thanks!
take a BOOL variable in ViewController controller and make the property and synthesize also.
and do this.
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
vc.check = YES;
in the view controller viewdidload
write this
if(self.check)
[mainbutton1 set hidden:YES];
The other answers should work unless...
Judging by your code I am going to guess that you are trying to hide a button on the viewController that presented the modal view?
If this is correct then what you are doing will not work as you are creating a new instance of ViewController which is not the already existing viewController you want to use.
Although the docs say that it is fine to call [self dismissModalViewControllerAnimated:YES]; from the presented modal view I tend to set up a delegate to handle the dismissal like in Apple's utitliy app template.
The reason this isn't working is because even though you have alloc'd and init'd the ViewController properly, the actual elements of that vc ViewController (including mainbutton1) have not been loaded yet.
Hitman has the right idea (and I'm voting his idea up).
Either put in a BOOL property for setting mainButton1 to hidden when the view appears, or call your [mainButton1 setHidden: YES] right after you explicitly display the view (via animation or adding subviews or whatever).
From your question it sounds like you want to hide the button in an existing view controller, whereas in your code you are creating a new one
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[vc.mainbutton1 setHidden:YES];
Either the view controller which you observe is not the one you expect or the mainbutton1 outlet is not connected properly. You can check if the memory controller is the one you expect by logging its memory address.
NSLog(#"Hid button for view controller %p", vc);
And doing the same in the viewDidAppear callback of ViewController
NSLog(#"In viewDidAppear for view controller %p", self);
It seems you want a certain button to be hidden if something has been happening somewhere else.
You COULD, somewhat as a hack (but I don't mind that very much) control this with a variable on your AppDelegate for instance.
When the "something" is happening "somewhere else", do this:
MyAppDelegate *appDelegate = [[(MyAppDelegate *)UIApplication sharedApplication] delegate];
appDelegate.shouldHideThatOtherButtonLater = YES;
Then, when you create your new ViewController later on you could use this value to determine if your button should be visible or not like this:
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
MyAppDelegate *appDelegate = [[(MyAppDelegate *)UIApplication sharedApplication] delegate];
[vc.mainbutton1 setHidden: appDelegate.shouldHideThatOtherButtonLater ];
You will in this case have to prepare your AppDelegate for this by creating and synthesizing that shouldHideThatOtherButtonLater-property.

What is the proper way to display a new view from a view and then returning to it in iphone

I have a simple app that have 3 views, HomeView, MenuView and GameView.
In the HomeView I have 2 buttons (Menu and Start Game). When the menu button is clicked, I open the MenuView using the following code:
- (IBAction)displayMenu:(id)sender{
MenuView *mv = [[MenuView alloc] init];
[self.view addSubView:[mv view];
[mv release];
}
In the MenuView, I have a button that will allow the user to return to the HomeView. When this button is clicked, I use the following code to return to the HomeView
- (IBAction)returnToHome:(id)sender{
HomeView* hv = [[HomeView alloc] init];
[self.view addSubView:[hv view];
[hv release];
}
The above code is working but is this the correct way of doing it? I was under the impression that when I call the addSubView, the view will be retain so If keep going back and forth between HomeView and MenuView, will i have multiple instance of HomeView and MenuView retained since I keep calling addSubView from each of the view?
Thank you.
You could use the UINavigationController, which will allow you to push UIViewControllers on to the stack.
Using the UINavigationController you will get an nice naviagtionbar in at the top of you screen and the back button.
You can find a nice example here:http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
I found this way the most useful and convenient. When calling the new view use this:
HomeView* hv = [[HomeView alloc] init];
(here you can add a uninavigation controller)
[self presentModalViewController:hv animated:YES];
Then to dismiss this view and go back use this:
[self dismissModalViewControllerAnimated:YES];
#atbebtg:
There is a way to do that, infact there are several, since there not really is a "right way" to do it.
For me this works well:
[[self navigationController] setNavigationBarHidden:YES animated:NO];
This will hide the Navigation Bar, so the user can't go back to the last screen.
The other thing you could do is to create your own subclass of UIViewController and not support the button event, like this:
- (IBAction)done:(id)sender
{
//inform the user, that going back is not possible, for example with UIAlertView
//[self.delegate infoViewDidFinish:self];
}
However, this solution seems a bit odd, because the user expects a existing button to work.
Still, this would work.
Others have given answers that present modal view controllers or build a navigation stack. In most cases I would use one of these approaches. Yet, the simplest way to fix the code in the question is to just remove the menu view from the super view. Something like this:
- (IBAction)returnToHome:(id)sender{
[self.view removeFromSuperview];
}

iPad Modal view not rotating

So I have a modal view displaying in my app that has a little info for the user to fill out. The problem is that when the device is rotated, some animation occurs, but only in the frame. The form itself does not rotate. All the autorotate options are set to YES. I am displaying it when the user clicks on a field in a popover. This makes me suspect it has something to do with that but I am not sure. It is bizzare because if the device is in either view and then the modal window is displayed, it is fine. It only happens when the device is rotated in the modal view. Does anyone have any idea what may be causing this behavior when the device is rotated? Thanks!
Here is a snippet that is handled in the popover view controller:
if (currentLevel == 1 && businessOrLocation == 0){
if(tbsViewController == nil)
tbsViewController = [[BusinessFilteredViewController alloc] initWithNibName:#"BusinessFilteredView" bundle:[NSBundle mainBundle]];
NSMutableArray *tempBusiness = [[NSMutableArray alloc] init];
for (id theKey in appDelegate.groupedBusiness) {
NSMutableArray *tempArr = [appDelegate.groupedBusiness objectForKey:theKey];
[tempBusiness addObject:tempArr];
}
tbsViewController.businessOrLocation = businessOrLocation;
tbsViewController.modalPresentationStyle = UIModalPresentationFullScreen;
tbsViewController.modalTransitionStyle = UIModalPresentationFullScreen;
[self presentModalViewController:tbsViewController animated:YES];
}
I ran into this problem as well. The fundamental problem is that popover controllers cannot present modal views—it seems that case wasn’t properly considered or designed for. In my situation, it was easy enough to work around. I just extended the delegate protocol for my popover-hosted view controller. The main view sets itself up as the delegate to the popover view, and takes responsibility for displaying and dismissing the modal views the user requests from within the popover.
Since I already had a delegate protocol to cleanly dismiss the popover view when the user clicks “done” it was only a small stretch to get autorotation working the way I wanted it to. Here are some snippets:
#protocol InfoViewControllerDelegate <NSObject>
#optional
// Implement this to close the info view once the user clicks done.
- (void)infoViewDidFinish:(InfoViewController *)view;
// Implement this method if the delegate launched us as a popup view and must therefore
// take responsibility for diplaying help.
- (void)infoViewDidRequestHelp:(InfoViewController *)view;
#end
And in my main iPad view which presents this popup view:
#pragma mark - InfoViewControllerDelegate methods
- (void)infoViewDidFinish:(InfoViewController *)view {
[self hideInfo:self];
}
- (void)infoViewDidRequestHelp:(InfoViewController *)view {
[self hideInfo:self]; // Close the info view first
HelpViewController *help = [[HelpViewController alloc] init];
help.delegate = self;
[self presentModalViewController:help animated:YES];
[help release];
}
To make life simple for cases where I am launching the info view outside of a popup view (for example, on the iPhone, it is a simple modal view), it checks to see if the delegate handles the modal subviews, and if not, handles them itself. That way I didn’t need to change the iPhone base controller at all, since autorotation already worked fine there. Here’s the “Help” button action in the info view controller, showing how I did that:
- (IBAction)help:(id)sender {
if ([delegate respondsToSelector:#selector(infoViewDidRequestHelp:)]) {
[delegate infoViewDidRequestHelp:self];
} else {
HelpViewController *help = [[HelpViewController alloc] init];
help.delegate = self;
[self presentModalViewController:help animated:YES];
[help release];
}
}
With this code in place, my entire interface autorotates smoothly on both devices, whether or not popup views were involved.
Just so i understand correctly... You are displaying a popover and inside that popover if the user taps a certain element then you are displaying a full screen modal view controller? Vie never tried that before and it seems odd for two reasons.
First it seems jarring to the user in my opinion. The popover gives you a nice, integrated UI and the modal takes you away.
More importantly though, your popover view controller doesn't really have authority over the whole screen so presenting a full screen modal from a popover just seems inherently wrong.
I would suggest you display a nav controller in the popover controller and instead of presenting the new view controller modally over the whole screen just push it on to the nav controller in the popover and keep the user inside the popover.
If that doesn't really work for you, then I would suggest reviewing your UI needs and redesigning the layout.
I am guessing that you implemented - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation in BusinessFilteredViewController and returns YES
Could you check that you add more than 1 subviews to application window . If so, try to create container UIViewController for all viewControllers that you want to add to window.

iPhone MailComposer class UIViewController dismissModalViewControllerAnimated issues

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!