InterfaceOrientation issues when popping a ViewController off the stack - iphone

I have the beginnings of my app working pretty well but I have one small issue that I can't figure out how to fix.
It's a view based app and I have a NavigationController in my appDelegate file which is pushing various ViewControllers as required. Going in the "forwards direction" everything works well, seems perfect actually, but the problem I have is when VC's are popped off the stack. This is the code I'm using to show the VC and then just using the back button to go back.
UINavigationController *iaxNC = [[UINavigationController alloc] init];
LogInViewController *logInVC = [[LogInViewController alloc] init];
[iaxNC pushViewController:logInVC animated:NO];
[logInVC release];
[_window addSubview:iaxNC.view];
[_window makeKeyAndVisible];
That loads my login view and then the code checks to see if there are any users before it loads the SetUp screen (if there are none) as follows:
setUpVC = [[SetUpViewController alloc] init];
setUpVC.firstUse = self.firstUse;
[self.navigationController pushViewController:setUpVC animated:YES];
[setUpVC release];
The problem happens when I hit the back button from the SetUp view to go back to the LogIn view.
I have a portrait and a landscape view for most of the VC's and these VC's are subclasses of Michael Tyson's TPMultiLayoutViewController. The portrait view is hooked up as *portraitView and also as *view, the landscape view is hooked up as *landscapeView.
The problem is this:
If I push a VC into view and change the orientation of the device before I hit the back button then when I do hit "Back" the previous VC is displayed in its original format (i.e. NOT rotated to the current orientation) it also appears that the lower left corner of the view is in the lower left of the screen. It then "sticks" like that until I rotate the device again and then all is good and functions as expected.
So the thing is I'm working through the contents of the TPMultiView subclass but I don't pretend to understand all of it yet (and therein probably lies my REAL problem) but as a stopgap solution is there a way to force the view of the "pusher" VC to appear in it's previous orientation (albeit briefly - and THEN allow it to rotate once it has been displayed) when the "pushee's" VC is popped?
Does that make sense?
SOLVED IT! The problem was I had included my own viewWillAppear method in LogInVC but I hadn't called [super viewWillAppear] so the effect of the superclass was never really going to work, was it! Thanks for the input. I can recommend the TPMultiView drop in subclass BTW works very nicely.

You should first verify your implementation of shouldAutoRotateToInterfaceOrientation: is correct for all your view controllers.
Then make sure the views of your view controller's have an appropriate autoresizingMask set. If you have created the view controllers and their views in Interface Builder you should set the resizing mask there.
Otherwise in your -loadView or -viewDidLoad methods you should have this line:
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;

Related

Removing a View from same view. Is it good practice.?

I have added following piece of code in a view.
- (IBAction)accept_clicked:(id)sender {
[self.view removeFromSuperview];
self.view = nil;
}
Once accept is clicked, I have removed the own view like this. It worked fine, anyhow is it fine to do like this or It should be removed from another view(parent)?
Don't do this (with self.view) - it doesn't looks good and you might face difficult to find problems. self.view is the main view associated with an UIViewController. So this view to be visible on the screen, you must have shown it somehow: either pushing it to a UINavigationController or presenting it modally with -presentViewController:animated:completion: (IOS5+) or - presentModalViewController:animated:. Showing a view by instantiating a view controller and adding its view to the current view controller's view is not a good practice also:
//Not good
MyViewController *mvc = [[MyViewController alloc] init];
[self.view addSubView:mvc.view];
In your particular case I suppose you are showing some terms and conditions (or something similar) and have an accept and deny button, your best solution would be to present your view controller from somewhere, implement a delegate method, so the presenting view controller can have the result and then in your -accept_clicked: method to use either [self dismissModalViewControllerAnimated:YES] or [self dismissViewControllerAnimated:completion:] (IOS5+),

load view controller when application becomes active

I will like my application that always starts as the first time I open it. I have several view controllers and when I exit my application and I open it again I see the view controller where I left of. Maybe I have to call the method applicationWillTerminate method.
I use this code to open a new view:
UIViewController *control = [[SomeViewController alloc] initWithNibName:#"SomeViewController"
bundle:nil];
UINavigationController *navControl = [[UINavigationController alloc]
initWithRootViewController:control];
[self presentModalViewController:navControl animated:NO];
[navControl setNavigationBarHidden:YES];
[control release];
[navControl release];
this code works great when linking it to buttons. But when I place that code in the applicationDidBecomeActive method it does not work.
The easiest way is to set UIApplicationExitsOnSuspend in Info.plist.
That really isn't the expected behaviour, though. Users expect to see the app "where they left off", especially if they've only briefly left the app (e.g. because they got a phone call).
Your code snippet adds a view controller, but is unlikely to work since your app delegate is not a UIViewController. It also doesn't do anything about removing the old view controllers.
EDIT: If all you need to do is display a splash screen (or something), then it's something like this:
In -applicationDidEnterBackground:, add a "splash screen" view (not a view controller) to self.window. (iOS takes a "screenshot" after you return from -applicationDidEnterBackground: and uses this in the app-switch animation; you want this to be what the user sees when switching back to your app)
In -applicationWillEnterForeground:, do whatever animations you want and eventually remove the view from the window (call -removeFromSuperview).
EDIT 2: The same will work in -applicationWillResignActive:/-applicationWillBecomeActive:, except this happens on a sleep/wake event, which might not be what you want...
I'd avoid using view controllers for this, because trying to shoehorn a view controller in the view controller hierarchy is likely to be problematic (for example, you have to figure out which VC to present it from, and you have to do the "right thing" if the user backgrounds your app while the VC is on screen, and...)
The reason it doesn't work in applicationDidBecomeActive is that method is only sent to the Application delegate, which doesn't know about presentModalViewController.
I suggest instead that in your appDelegate, implement applicationWillEnterForeground:, which should restore the state to a newly launched application (equivalent to what the state is at the end of application:didFinishLaunchingWithOptions: ).
OR...(edits)
If you just want a certain viewController to run (which is still loaded, right?)...For example, if you have a tab controller and just want to go to the root of the first view controller, put the following code into applicationWillEnterForeground:
UITabBarController * myTabBar = self.tabBarController;
myTabBar.selectedIndex = 0;
[[myTabBar.viewControllers objectAtIndex:0] popToRootViewControllerAnimated:NO];
Temporary solution:
I made my application crash on applicationWillResignActive method and it works. My application needs to run an animation when I launch it. But this works because next time the application runs it starts like the first time I opened it.

Allowing autorotation inside a programmatically created view that is displayed modally

This has been asked a dozen times on this site, but I haven't found an answer that works for me. I have an iPad application with a UISplitViewController on the root level that is created programmatically. Inside the view that is being displayed in the right hand pane, triggered by user interaction, a UINavigationController is programmatically created and presented to the user. Here is that code:
listenerController = [[UINavigationController alloc] initWithRootViewController:listenerView];
[listenerController.navigationBar setTintColor:[UIColor colorWithRed:185.0f/255.0f green:80.0f/255.0f blue:0.0f/255.0f alpha:1.0f]];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[listenerController setModalPresentationStyle:UIModalPresentationFormSheet];
[listenerController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[listenerController setModalInPopover:YES];
}
[self presentModalViewController:listenerController animated:YES];
[listenerController release];
This does create the view controller properly, but when it is displayed the iPad is forced back into portrait view regardless of what orientation I have the iPad in. Then when I dismiss the modal window, it will rotate back.
I already have shouldAutorotateToInterfaceOrientation in the viewcontroller of the righthand pane set to YES, and I even tried adding this to the main app delegate class without any luck. It doesn't seem like I should have to subclass UINavigationController just to override the shouldAutorotateToInterfaceOrientation method.
Am I calling presentModalViewController from the wrong object? I've tried [self presentModalViewController ...] as well as [self.parentViewController presentModalViewController ...] with the same results.
I'm assuming that the self in your code example is the right-view (detail) view controller. You need to call presentModalViewController from the root UISplitViewController.

Layout screws up when I push several controllers without animation

So I have a stack of three UITableViewControllers, each of which displays its view correctly underneath the navigation bar when I tap through the UI manually.
However, now I'm working on restoring state across app restart, and so I'm pushing the same two controllers on top of the root view controller, one at a time, in the same method in the main thread. What ends up happening then is that the middle controller's view is laid out too far down, and the top controller's view is too far up (underneath the nav bar).
Relevant code:
for (int i = 0; i < [controllerState count]-1; i++) {
MyViewController* top = (MyViewController*)navigationController.topViewController;
int key = [[controllerState objectAtIndex:i] integerValue];
[top restoreNextViewController:key]; // this calls pushViewController:
// so top will be different in the next iteration
}
I suspect that the problem is that I'm not allowing some important UI refresh process to take place in between the two pushes, because they happen in the same thread. It almost looks as though the automatic view adjustment that's supposed to affect the top controller affects the middle one instead.
Is that right? And if so, what should I do, since I do want all the state-restoration to take place immediately upon app start?
EDIT: looks like I was unclear. restoreNextViewController: is a MyViewController subclass method that restores the controller's state based on a stored key, and then pushes the appropriate child controller with [self.navigationController pushViewController:foo animated:NO]. I'm doing this because my actual app, unlike this simplified case, has up to 6 controllers in the stack, and they're not always the same ones. So I figured this would be a cleaner design than going down the stack checking controllers' classes. Each controller already knows how to push a child controller in response to user input; why not reuse that on app restart?
I'm not having any trouble getting the controllers to show up; they're just being laid out strangely.
Have you considered having each view controller push its child during viewWillAppear:? I would just set an isRestoringState property in your appDelegate and check that during viewWillAppear:, if it's set, run your restore for that view, including pushing any visible child view. Something like:
- (void)viewWillAppear:(BOOL)animated {
if ([[UIApplication sharedApplication] isRestoringState]) {
// restore local state, set myChildController if a child is visible
if (myChildController) {
[self.navigationController pushViewController:myChildController animated:NO];
}
}
}
You can call pushViewController:animated: multiple times. Please post the code for -restoreNextViewController. It's confusing why this code is as complex as it is. Have you read the section of the View Controller Guide on Creating a Nav Controller? They cover pushing view controllers on the stack at startup.
I agree with Rob, I would look at implementing UINavigationController when your application is loaded. Then you would push each of your controllers onto UINavigtionController in the sequence you want them layered.
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
So if you know what state you want to bring your application back to you could load the appropriate ViewController in the series to be latered and push them onto the NavigationController.
UIViewController *myViewController = [[UIViewController alloc] initWithNibName:#"myView" bundle:nil];
[navigationController pushViewController:myViewController animated:NO];
Then if you want to "pop" back to the UIViewController either the user can press the "Back" button on navigationbar for free. Or you can control the navigation with
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
eg [[self navigationController] popViewControllerAnimated:NO];
You can also control whether you want the NavigationBar to show or not if you want to control the push/pop yourself. I hope I've explained this well enough I've just traveled this road recently myself.
I ended up manually resetting each table view's content inset during its controller's viewWillAppear:.

Adding a subview into view hierarchy

I'd like to have a view appear when the user clicks a button. The hierarchy I have looks like this:
MainWindow
-UIView
--ScrollView
---ScrollView.pages = UIViews
----UIView (from above assignment)
----TextView
----InfoButton
pages is an NSMutableArry of pageController objects. These hook to a nib. These nibs are the pages that user flicks through in the scroll view.
The InfoButton click is wired up like this:
- (IBAction) infoButton_click:(id)sender{
topView topViewViewController *topView = [[topViewViewController alloc] initWithNibName:#"TopView" bundle:[NSBundle mainBundle]];
//[self.navigationController pushViewController: topViewView animated:YES];
//[self.view addSubview: topViewView.view];
[super.view addSubview: topViewView.view];
[topViewView release];
}
InfoButton is on one of the pages in the ScrollView. I've commented out different code that has been tried. None of it adds the view. Nothing happens. Is there a way to get TopView as the top view in the hierarchy?
Is your goal to add the view as a subview, or to slide on a new view using the navigation controller? I'm going to assume the latter for the moment.
- (IBAction)infoButton_click:(id)sender
{
TopViewController *topViewController = [[TopViewController alloc] initWithNibName:#"TopView" bundle:nil];
[self.navigationController pushViewController:topViewController animated:YES];
[topViewController release];
}
This is correct if you actually have a navigationController. Make sure you actually do. When "nothing happens" in Cocoa, it usually means something is nil. You should check in the debugger or with NSLog() to see if any of these values are nil. It is possible (even likely), that your parent has a navigationController, but you do not.
Classes should always have a leading capital. Do not create a variable called "view" that is of class "UIViewController". This is a sure path to suffering. Objective-C is a dynamic language with limited compiler checks on types. Naming things correctly is critical to effective programming in ObjC.
Based on your comment to a previous answer, you want to present a modal view. You do this by creating a new view "modalView" and calling [topView presentModalViewController:modalView animated:YES].
In a future version of the iPhone OS, which of course I would be unable to comment upon if it were under NDA, you might be able to present a modal view controller with a flip transition by setting a property on the view controller to be presented, which would probably be called modalTransitionStyle or somesuch.