Modal View Controllers appear behind Action Sheet - iphone

My modal view controllers are being shown behind my UIActionSheet, and my UIActionSheet is not getting dismissed. I am using:
[self presentModalViewController:composeTweetView animated:YES];
To present my modal view controller.
My action sheet is being shown from the tabBar:
[actionSheet showFromTabBar:self.parentViewController.tabBarController.tabBar];
This code worked on iOS 4.0.
if (buttonIndex == 0) {
if (self.isLoggedIn) {
[FlurryAPI logEvent:#"ST_REPLY_CLICKED"];
composeTweetView.isDirectMessage = FALSE;
[self presentModalViewController:composeTweetView animated:YES];
[composeTweetView release];
}
else {
LoginViewController* loginView = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
loginView.delegate = self;
loginView.isPostingComment = TRUE;
self.isReply = TRUE;
[self presentModalViewController:loginView animated:YES];
[loginView release];
[composeTweetView release];
}
}
Summary:
I have a UIViewController that contains a UITabBar. I am presenting a UIActionSheet which has a few buttons that present a modal view controller. When the modal view controller is presented, the UIActionSheet should dismiss itself and the modal view should be on the top of the stack. The problem is, the UIActionSheet does not dismiss, and the modal view is loaded behind it. This problem did not occur up until iOS 4.2.1
Steps to Reproduce:
Create a TabBar project, setting
your Base SDK to iOS 4.2.1
Create a button or trigger to show a UIActionSheet
Allow one of the buttons in the UIActionSheet to present a modal view controller using the syntax: [actionSheet showFromTabBar:self.parentViewController.tabBarController.tabBar];
Expected Results:
1. The UIActionSheet should dismiss itself, and the modal view should appear in front
Actual Results:
1. The UIActionSheet does not get dismissed and the modal view appears behind it.
Regression:
This problem was not apparent prior to iOS 4.2.1
Notes:
I have tried other ways of displaying the UIActionSheet all of which don't work as intended:
//[actionSheet showInView:self.parentViewController.tabBarController.tabBar];
//[actionSheet showInView:[self.view window]];
//[actionSheet showInView:self.parentViewController.tabBarController.view];
//[actionSheet showInView:[UIApplication sharedApplication].keyWindow];

In putting together a sample in order to reproduce your problem I took a look at the UIActionSheet delegate methods. I believe you can use:
actionSheet:didDismissWithButtonIndex:
instead of
actionSheet:clickedButtonAtIndex:
I haven't tested it, but I believe you still get the same button number and it doesn't fire until the actionsheet has disappeared from the view.

I don't understand your problem sorry but everything works great for me. I have tried the following code in Simulator and on 4.2.1 iPod Touch 4G (both worked)
- (IBAction)doit {
UIActionSheet *actionSheet = [[UIActionSheet alloc]initWithTitle:#"title" delegate:self cancelButtonTitle:#"not OK" destructiveButtonTitle:#"Absolutely" otherButtonTitles:nil];
[actionSheet showFromTabBar:self.tabBarController.tabBar];
[actionSheet release];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
UIView *v = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
v.backgroundColor = [UIColor redColor];
[self.view addSubview:v];
[v release];
}
}
created the TabBar sample project
added button to firstView-nib and connected with the appropriate IBAction (have to name the FileOwner to FirstViewController)
set the delegate method in FirstViewController.h (<UIActionSheetDelegate>)
added the code above
//EDIT: ok I saw that you want to present it modally but even this works for me on 4.2.1
TMPController *tmp = [[TMPController alloc]initWithNibName:#"TMP" bundle:nil];
[self presentModalViewController:tmp animated:YES];
[tmp release];
maybe it works because I use self.tabBarController.tabBar, try that

Not sure if you have already resolved this. But I didn't see a clear answer above. I was having exactly the same problem as you. So in the end, it was an issue on my side, but what help me debug this was to add the actual action code in the method
actionSheet:didDismissWithButtonIndex:
as suggested by Matthew.
This proved that the view was actually dismissed. That's when I realized that I put the UIActionSheet alloc in my viewWillAppear method. So each time the view appears it re-creates the action sheet.

This seems weird as the SDK Documentation states:
actionSheet:clickedButtonAtIndex:
…
The receiver is automatically dismissed after this method is invoked.
I can think of 3 possibilities why it may not disappear:
The main runloop (main thread!) which handles the animations and display stuff is not called. Do you work something heavy in your main thread like synchronous networking calls? (note the word "after" in the SDK text)
Somehow you schedule to show the action sheet multiple times
You display the same view controller instance modally that is already somewhere below the current view on the view stack.

I just wrote a quick sample app to test this out and it worked just as I expected (i.e., I couldn't repro your issue). It was a little different in that I didn't do a TabBar application but I'm still hopeful this helps. What I did with the UIActionSheet was to show it like this: [actionSheet showInView:self.view]. Maybe that will work?
Sorry for the rushed answer, I was on my way out when this caught my eye. :)

I have also faced problems like this with iOS 4.2.
I think you should try with the following steps:
Create a separate method for the code you want to be executed on clicking the actionsheet button. suppose the method is -(void)presentModalView{}
2.Now in the - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex method,Call the presentModalView{} method for the clicked button index like this:
if(buttonIndex==0)
{
[self performSelector:#selector(presentModalView) withObject:nil afterDelay:0.3];
}
3.You can also try by different delay time.
Hope it helps..

I had the same issue calling MailComposeView from AlertBox/ActionSheet caused the MailCompseView to come behind invisible screen.
I solved it by calling dismissWithClickedButtonIndex:animated: in the actionSheet button handler.

The problem has been resolved in iOS5.

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!

UIAlertView + Modal View Controller doesn't work

I have a UIAlertView that has the buttons "OK" and "Cancel". I'd like to present a modal view controller when the OK button is pressed. Here's what I have done so far:
Created the UIAlertView box. Implemented UIAlertViewDelegate protocol. Implemented (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex method.
In the above method, when buttonIndex == 0, I'm trying to do something to the effect of:
if (buttonIndex == 0)
{
ModalViewController *mdvc = [[[ModalViewController alloc] initWithNibName:nil bundle:nil] autorelease];
[self presentModalViewController:mdvc animated:YES];
}
As it turns out, the modal view does not present itself. I tried many other approaches but they are just making it complex and making me create a lot of unnecessary variables. There MUST be an easier way.
Some Extra Information:
If it matters in anyway, this is an OpenGL ES application.
If I invoke [self presentModalController:] as a result of a UIButton press, it does work as expected - I see the modal view controller.
I was having this problem as well. It seems clear the alert must be gone before the modal can present, so you're looking for a way to know the alert is gone. Skimming the docs, there is a simple way.
Instead of presenting your modal when this is called:
- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex
Use:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
This worked like a charm for me. From the documentation:
This method is invoked after the animation ends and the view is
hidden.
Alright... so I fixed this myself. As I said, this was in an OpenGL ES application, so I set some global bools and called the [self presentModalViewController] in the drawFrame method. This is definitely not the best method, I know, but in the time-crunch I am in, there seems to be no better solution!
The issue is certainly delay-related but performSelector:withObject:afterDelay doesn't seem to be sufficient!
First, move that code to a new method named presentModal:
- (void)presentModal:(UIViewController *)mvc {
[self presentModalViewController:mvc animated:YES];
}
Then, in the method where you handle the response from the UIAlertView, call this new method, like this
ModalViewController *mdvc = [[[ModalViewController alloc]
initWithNibName:nil
bundle:nil] autorelease];
[self performSelector:#selector(presentModal:)
withObject:mdvc
afterDelay:0.0];
That will postpone the execution of the presentModal: method until after the method handling the UIAlertView has returned, and after the UIAlertView has been removed from the screen.
The Cancel button has index of 0, the OK button will have index of 1. Are you sure you are doing the action on the correct button index? (i.e. if you press 'Cancel' does it show the modal?).

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];
}

Modal view controller won't dismiss itself

What I'm doing:
In my app, I'm presenting a modal view controller (containing app settings) using the following code:
optionsViewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentModalViewController:optionsViewController animated:YES];
This transition just curls up the bottom part of the view to expose a few settings. (See the 'Maps' app for an example.) When you tap on the top half of the page, where the original view is still there but grayed out, the modal view controller is automatically dismissed (handled by the OS, I didn't code for this).
-
What's not working:
This is working fine in iOS 4 (my app is currently on the App Store in fact). But in iOS 5, it looks like Apple have changed the behavior of this transition, and the view controller no longer dismisses itself. I'm trying to replicate the behavior that was handled by the OS before, but can't figure out how to.
-
What I've tried:
Adding an invisible button to the top of the options view doesn't work. The page then curls up the full way, which I don't want.
Apart from this, I'm stuck. How should I replicate how this worked originally (or was I doing it the wrong way from the start!). Any help is much appreciated!
Dude, I ran into the same problem.. and here is what I found about using parentViewController:
Note that as of 5.0 this no longer
will return the presenting view
controller.
This was written in the header file of UIViewController...
I am using ShareKit, and the modalViewController was working perfectly in iOS4, but in iOS5, it just won't dismiss itself! This is because in their code, they are using:
[[currentView parentViewController] dismissModalViewControllerAnimated:animated];
and parentViewController will return nil, since this is a modal presented view controller...
By searching for a solution, I found your question.. So, I decided to fix it myself :P
I changed the previous line to this:
[currentView dismissModalViewControllerAnimated:YES];
Works like a charm.
EDIT: Depending on how you interpret the original question, there are two answers. Here's the second:
In iOS5 it seems that the modal controller only dismisses itself when you click the curl, but not above the curl or the backgound. In iOS5, in order to actually get the modal view to dismiss itself when tapping the background or above the curl I added the following code to the controller, to listen to taps on the modal view, but ignore taps to buttons. This should mimic the behavior in previous version of iOS when working with a modal controller with page curl.
- (void)viewDidLoad
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
tap.numberOfTouchesRequired = 1;
tap.delegate = self;
[backgroundView addGestureRecognizer:tap];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
//change it to your condition
if ([touch.view isKindOfClass:[UIButton class]]) {
return NO;
}
return YES;
}
- (void)handleTap:(UITapGestureRecognizer *)sender {
[self dismissModalViewControllerAnimated:YES];
}
What's the code you're using to dismiss the modal view controller? I've seen code like this:
[self.parentViewController dismissModalViewControllerAnimated: YES];
that doesn't work on all versions of the OS. However, this:
[self dismissModalViewControllerAnimated: YES];
should.
I had the same problem, also affects those who use:
[self.parentViewController.parentViewController dismissModalViewControllerAnimated:YES];
I fix it with a Observer, adding this where you had the dismiss:
[[NSNotificationCenter defaultCenter] postNotificationName:#"yourObserverName" object:self];
And this in the parent parent view controller:
// add in viewDidLoad for example
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissModalVCFromParent:) name:#"yourObserverName" object: nil];
//The function
- (void) dismissModalVCFromParent:(NSNotification *)notif
{
[self dismissModalViewControllerAnimated:YES];
}
// Don't forget remove
[[NSNotificationCenter defaultCenter] removeObserver:self];
This seems to work on the (now final version of) ios 5.
I notice that you have to tap is a specific region to dismiss the page curl - tapping near the edges of the top portion of the screen does not seem to do anything, but the center, blurred section above the page curl graphic consistently results in dismissing the modal view.
I'm not sure whether that narrow tap region behavior is new to ios 5 or already existed and I never noticed before. Hopefully that is helpful!
In iOS5 it seems that the modal controller only dismisses itself when you click the curl, but not above the curl or the backgound. In iOS5, in order to actually get the modal view to dismiss itself when tapping the background or above the curl I added the following code to the controller, to listen to taps on the modal view, but ignore taps to buttons. This should mimic the behavior in previous version of iOS when working with a modal controller with page curl.
- (void)viewDidLoad
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
tap.numberOfTouchesRequired = 1;
tap.delegate = self;
[backgroundView addGestureRecognizer:tap];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
//change it to your condition
if ([touch.view isKindOfClass:[UIButton class]]) {
return NO;
}
return YES;
}
- (void)handleTap:(UITapGestureRecognizer *)sender {
[self dismissModalViewControllerAnimated:YES];
}
Thanks guys, this saved me a lot of time. I just noticed that the presentModalViewController and dismissModalViewController methods are deprecated according to the source code for UIViewControoler.h. There are alternative presentViewController and dismissViewController methods.

last Button of actionsheet does not get clicked

I have used an actionsheet in my project and when it appears it show all buttons but last (4th) button does not responds to my click(only it's half part responds)..
I know the reason it is because i have used a TabBarController and the present class is inside that tabbar controller....
only that part of the actionsheet is responding which is above the tabs....and my last button is half above and half is on top of tabbar
please help
i suggest using this:
[actionSheet showInView:[UIApplication sharedApplication].keyWindow];
I had the same problem that you have and using this method to show it worked for me. The TabBar wants to stay key Window what makes your bottom button appear above, but is actually under the tabbar.
Hope this does the trick for you..
Edit
If you use landscape mode and the method above doesn't work. You can use the following fix:
#Vinh Tran: [sheet showFromTabBar:self.parentViewController.tabBarController.tabBar]
What method do you use to show your actionsheet. Try showFromTabBar: method
The real problem comes in, when your interface is rotated to landscape and the parent view controller has a transformation on it. Believe me, that's a realistic scenario, doh. Then the action sheet is clipped and you can't use the parentViewController because it is transformed. The solution to avoid all these issues is to create a new window, add a rotatable view controller as rootViewController and use its view to display the sheet.
CGRect applicationRect = [[UIScreen mainScreen] bounds];
UIWindow* actionSheetWindow = [[UIWindow alloc] initWithFrame:applicationRect];
RotationViewController* rootViewController = [[RotationViewController alloc] initWithNibName:nil bundle:nil];
actionSheetWindow.rootViewController = rootViewController;
[rootViewController release];
actionSheetWindow.hidden = NO;
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle:nil];
[actionSheet setCancelButtonWithTitle:#"Cancel" handler:^{
actionSheetWindow.hidden = YES;
[actionSheetWindow release];
}];
[actionSheet showInView:rootViewController.view];
The code above uses BlocksKit, but you can do it also by using the actionSheet delegate and instance properties.
RotationViewController is just a UIViewController subclass that implements
- (void) viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
self.view.opaque = NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}