switching between viewControllers - iphone

I've used this method on several apps that are for sale on the app store, but for some reason the current app I am working on is driving me nuts... I must be overlooking something.
The app's main viewController .h file:
#import "MainMenuController.h"
#import "GamePlay.h"
#interface ProjectNameiPhoneViewController : UIViewController <MenuDelegate, GameDelegate> {
UIViewController *currentPageController;
}
The app starts up and loads the MainMenu viewController:
UIViewController *nextController = [[MainMenuController alloc] initWithNibName:#"MainMenuController" bundle:nil];
[nextController performSelector:#selector(setDelegate:) withObject:self];
[self.view addSubview:nextController.view];
currentPageController = nextController;
[currentPageController retain];
[nextController release];
From MainMenuController.m, the user chooses to start the game:
[delegate startGameplay:self];
Back in the app's main viewController:
- (void)startGameplay:(MainMenuController *)sender {
UIViewController *nextController = [[GamePlay alloc] initWithNibName:#"GamePlay" bundle:nil];
[nextController performSelector:#selector(setDelegate:) withObject:self];
[currentPageController.view removeFromSuperview];
[self.view addSubview:nextController.view];
[currentPageController release];
currentPageController = nextController;
[currentPageController retain];
[nextController release];
}
From the gameplay screen, user hits the back button to return to the main menu:
- (IBAction)backTapped {
[delegate backToMenu:self];
}
Back in the app's main viewController:
- (void)backToMenu:(GamePlay *)sender {
UIViewController *nextController = [[MainMenuController alloc] initWithNibName:#"MainMenuController" bundle:nil];
[nextController performSelector:#selector(setDelegate:) withObject:self];
[currentPageController.view removeFromSuperview];
[self.view addSubview:nextController.view];
[currentPageController release];
currentPageController = nextController;
[currentPageController retain];
[nextController release];
}
I once again choose to start the game from the main menu.
The GamePlay class/Nib loads, and I once again click the back button to return to the main menu.
At this point the app crashes, with no information printed to the console.
Any ideas would be GREATLY appreciated - I've commented out almost everything else in my code to the point where this switching between viewControllers is practically the only code being run and I'm at a loss as to why it is crashing...
Thanks so much in advance for your help!

It's likely that you want to have the MainMenuController as the rootViewController of your application's keyWindow.
Additionally, UIViewController's views should not be added to other UIViewController's views as this breaks the responder chain and in iOS 5.0, will throw an exception. If you choose to only implement it as an iOS 5.0 application, then I recommend taking a look at [UIViewController -addChildViewController:] and [UIViewController -removeChildViewController:]
In saying this, a better solution if doable, would be to have the MainMenuController as the rootViewController as stated above, and then to call [UIViewController -presentModalViewController:animated:] in your startGameplay method and use the delegate to dismiss the modal view controller

This doesn't seem like a true resolution but the app only crashes when I'm running it through Xcode's "build and run." If I open the app by clicking the icon on the device itself, the app runs perfectly. I can play it for long periods of time and mash the buttons to switch between view controllers over and over again very quickly, and it does not crash. So I'm going to submit it for the App Store even though it crashes every time without an error message when running through Xcode - it absolutely does not crash on the device itself.
The strangest part about the crash when running through Xcode is this:
The app crashes on the device, exiting out to the home screen. However, Xcode still shows the app as "Running" and I have the option to click "Stop" - it is not grayed out. As I said, no error message is printed to the console.

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!

Get 'EXC_BAD_ACCESS' signal when invoking [UINavigationController pushViewController]

In order to speed up my app, I've create three different UIViewController in AppDelegate and it has readonly property for the controllers. Those controllers are used for navigation controller.
If I tap a button on the root view, I just show another view using pushViewController method. Let me show you some code for this here.
UIViewController* controller = delegate.anotherViewController;
[delegate.navigationController pushViewController:controller animated:YES];
At first time, this work well, but if I navigate back and tap the button again, I've got a signal 'EXC_BAD_ACCESS' at second line.
What's wrong? And, how can I prepare all of my view controllers at the beginning, not create them when they are needed?
Most of the time EXC_BAD_ACCESS means that you've released an object and you're trying to reuse it without retaining it.
Look if you have released your viewController too early and whether you are (re)using it the right way or not...
I had the same problem. My code was
AddMedia *info = [[AddMedia alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:info animated:YES];
[info release];
I was releasing my viewCOntroller which was crashing the app.
When I commented that line, It worked seamlessly. The code after the change is:
AddMedia *info = [[AddMedia alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:info animated:YES];
// [info release];

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.

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!

Why isn't removeFromSuperview hiding my UIView in my iPhone application?

I'm new to iPhone development and am having problems removing a sub-view from the main window. The problem is that the view still shows up even after calling removeFromSuperview.
The sub-view is created and added to the display tree through this code:
// Instantiate the controller for the authentication view
AuthenticationController* controller = [AuthenticationController alloc];
[controller initWithNibName:#"AuthenticationView" bundle:[NSBundle mainBundle]];
authController = controller;
// Add the authentication view to the window
[[stateManager appWindow] addSubview:[authController view]];
Then later, and I have verified that this code is run by setting a breakpoint, this is how I'm attempting to remove the view:
[[authController view] removeFromSuperview];
In case it matters, here's the dealloc code that does the for the owner of the view controller:
- (void)dealloc {
[authController release];
[super dealloc];
}
What is causing this sub-view to continue to show up?
I got this working. Apparently, a view doesn't go away until it's deallocated, and I had a misunderstanding of how memory management works on this platform. Here's my corrected code:
AuthenticationController* controller = [[AuthenticationController alloc]
initWithNibName:#"AuthenticationView" bundle:[NSBundle mainBundle]];
controller.delegate = self;
authController = controller;
[controller release]; // <-- Problem was that a reference was being maintained
[[stateManager appWindow] addSubview:[authController view]];
Not sure what you mean by "show up." On screen? In memory?
Your "fix" looks buggy, in that alloc gives you one reference, you then release it, which gets rid of the AuthenticationController. Which you then use.
This might appear to work since there hasn't been anyone overwriting the controller before you read its view, but this is just asking for trouble.