I'm trying to implement auto-rotation in my application that is basically UINavigationController with lots of UIViewControllers that get pushed onto it.
I've copy-pasted this in my first UIViewController (that gets pushed into UINavigationController):
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Everything worked fine... However, if I paste in that code into second UIViewController (that first one pushes on top after some button click) - autorotation won't work. shouldAutorotateToInterfaceOrientation gets called when UIViewController is first initialized, but after it is visible and I rotate device - nothing happens.
So result is: first view gets rotated well - portrait/landscape... but after I click button and get into second view I remain stuck into that portrait or landscape, whatever was active.
I tried subclassing UINavigationController and setting shouldAutorotateToInterfaceOrientation there, but that also doesn't work.
What am I doing wrong?
There's a bug in the API that doesn't cause it to work for the second view. I solved it originally using setOrientation, but that's a private API and thus not a reasonable solution. I haven't released any new versions of the application while I try to figure out alternatives (and I don't think having customers upgrade to OS 4.0 is a solution). I'm thinking I'll need to manually keep track of the orientation and rotate my views manually to counteract the effects of the wrong rotation.
You need to implement this method in all views in the hierarchy
Related
I'm working on an iphone app with a TabBarController as rootcontroller. And this rootcontroller is associated to Navigation Controllers (each one related to a specific Tab Bar button).
One on these navigation controllers shows some photos and thus I needed to enable Lanscape which obviously did not work until I added this code to all view controllers
- (BOOL)shouldAutorotateToInterfaceOrientation (UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
However, now that all orientations are enabled in all views some views I didn't want in landscape are showing very ugly :(
Any idea how to just leave the landscape orienation only on this photo view and disable it on all other views?
Consider showing the UIViewController that needs full reorientation capabilities modally.
That would be the common and in my humble opinion correct way to handle such situation.
To answer your subject in short: yes, it does have to return YES if you want any of the tabbed viewControllers to allow reorientation. The same goes for viewControllers within the stack of your UINavigationControllers. Hence the same goes for any combination of those.
From Apple's Technical Note on that subject:
Why won't my UIViewController rotate with the device?
All child view controllers in your UITabBarController or
UINavigationController do not agree on a common orientation set. To
make sure that all your child view controllers rotate correctly, you
must implement shouldAutorotateToInterfaceOrientation for each view
controller representing each tab or navigation level. Each must agree
on the same orientation for that rotate to occur. That is, they all
should return YES for the same orientation positions.
In case using a modally presented view controller is no option for you - here comes another way...
There is also a solution possible that seems a little "hacky" but worked for me in the past. I will draft it in a very very "hacky" way to simplify the answer.
Within the shouldAutorotateToInterfaceOrientation:toInterfaceOrientation implementation of all of your viewControllers, return a global value. Change that global value according to the needs of the currently displayed viewController. Even though it is said that a viewController is only asked once, this common rumor has proven untrue for my. So here comes the super hack;
All view controllers within your navigation stack should implement the shouldAutorotate-method like this:
- (BOOL)shouldAutorotateToInterfaceOrientation (UIInterfaceOrientation)toInterfaceOrientation
{
extern BOOL gSouldAutorotateToAnyOrientationFlag;
if (gShouldAutorotateToAnyOrientationFlag)
{
return YES;
}
return UIInterfaceOrientationIsPortrait(toInterfaceOrientation);
}
Now, somewhere in your app, you should declare and instantiate that global flag - you could place this ugly global flag within your app-delegate implementation, directly below the imports and above the #implementation block:
BOOL gShouldAutorotateToAnyOrientationFlag = NO;
Within all viewControllers that are supposed to be shown in portrait mode only, set that flag towards NO - e.g. within viewWillAppear;
- (void)viewWillAppear:(BOOL)animated
{
extern BOOL gShouldAutorotateToAnyOrientationFlag;
gShouldAutorotateToAnyOrientationFlag = NO;
}
Within the viewController/s that are supposed to be shown in any orientation, set that flag towards YES - again within viewWillAppear;
- (void)viewWillAppear:(BOOL)animated
{
extern BOOL gShouldAutorotateToAnyOrientationFlag;
gShouldAutorotateToAnyOrientationFlag = YES;
}
That way, whenever the entire navigation stack is asked for its orientation capabilities, the proper answer will be given. From my experience, the entire stack is asked, over and over again and answers are not cached, hence my hack worked whenever I needed it. Still, the rumor seems to be widespread that those answers are somehow cached and therefor that rumor might be valid in certain cases - hence I shall not be held responsible if this does not work for you (or even down voted :D )
On the view you only want landscape for:
- (BOOL)shouldAutorotateToInterfaceOrientation (UIInterfaceOrientation)toInterfaceOrientation
{
return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
}
You can replace UIInterfaceOrientationIsLandscape with UIInterfaceOrientationIsPortrait if that better suits your needs.
I have a nice little app on the app store that does pretty well for itself. Life was great until iOS 5 came to town. Now, I have a number of issues with my app that I have no way of fixing because I have no clue what is going on, because I feel that they are iOS 5 issues, not mine.
Was there an iOS 5 conversion manual I missed? Or did they just change everything for fun, and want us to figure out where all the easter eggs were?
Here is another issue I am experiencing (that I have wasted so much time trying to fix), that DON'T EXIST AT ALL when I simply say that I want to run the app in good ol' 4.2:
Modal view
My app is a simple reader app. I have a book reading view that displays text with a UIWebView. One of the features I have been working on involves the ability to take notes as you read. This is achieved by hitting a button, and presenting a modal view. Yes, a modal view. The most simple pre- iOS 5 thing you could possibly do. Now, when I dismiss my modal view, just by hitting cancel, and simply dismiss the view, when I get back to my reader view, the navigation bar at the top is pushed up half way off the screen! This doesn't happen in 4.2, but there it is in iOS 5!
What can I do to get this issue resolved?
Thanks for your help.
Ok, I was just able to figure out what in the blazes was going on. I had the shouldAutorotateToInterfaceOrientation value set to a BOOL variable, so that when the modalView was coming back, it didn't know the state/size of the status bar. Fixed that, and the problem disappeared.
I have the feeling it has something to do with the way you present and dismissing the modalview. Apple introduced a new method to present views. May you try using theses instead of the old ones and see if it fixes your problem.
So here is what you do:
change this method:
presentModalViewController:animated:
into the new preferred method introduced with iOS 5:
presentViewController:animated:completion:
Depending if you are using dismissModalViewControllerAnimated:to dismiss your view, change it into dismissViewControllerAnimated:completion.
This methods also have completion handler which is very useful to do some extra work after the view has been presented/dismissed. Maybe that also helps with your other issue. Let me know if that might helped.
A major change in iOS 5 is that the navigationController property of UIViewController is no longer set for modal views. Instead, there is a new (not present in iOS 4) parentViewController property. So where you're using navigationController in a modal view you need to change the logic to something like:
UIViewController* parent;
if ([self respondsToSelector:#selector(parentViewController)]) {
parent = self.parentViewController;
}
else {
parent = self.navigationController;
}
(That's from memory, so I can't guarantee that every t is dotted and every i crossed.)
I was seeing this same clipping problem.
I found out that the reason for my issue was that I set the content size within the modal dialog (something I did for my iPad layout), so removing these two lines seemed to fix the issue:
CGSize size = CGSizeMake(320, 480);
self.contentSizeForViewInPopover = size;
I thought the problem was fixed but it wasn't. After reviewing the code some more, cleaning the build, and retesting it turned out to be a shouldAutorotateToInterfaceOrientation which would return NO for all orientations, for a brief amount of time (flag == NO) while the app is loading (root controller). You want to at least return YES to one orientation like so:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return !self.flag ? UIInterfaceOrientationPortrait == toInterfaceOrientation : YES;
}
I am new to iOS and using storyboards for the first time. When my app starts it checks back with the a server app I have written to see if the saved credentials are authenticated and I then in my AppDelegate class I then attempt to show the appropriate scene in the app's storyboard - MainMenu if authenticated or a Login Screen if not authenticated.
I have tried using instantiateViewControllerWithIdentifier on the storyboard and also the performSegueWithIdentifier on the initial NavigationController which is set to be the "Initial View Controller" to display the appropriate view..
However with both methods only the blank navigation bar shows and I am unsure where to go from here.
If there was some example code on how others manually manipulate storyboard scenes and viewcontrollers that would be great. Am I maybe putting the code in the wrong place (ie should it go into the first View Controller) or should that not matter? No exceptions are raised and I seem to have access to instantiated objects as required.
I am thinking I need to understand the operation of the app delegate's window more, or maybe should I focus on manually loading the storyboard by removing it's reference from the InfoPlist settings?
Any thoughts would be greatly appreciated.
From my (admittedly haphazard) understanding of storyboards (at the moment), you should have two named segues going from a first viewcontroller, and then you can simply trigger one or the other as need be (I presume there's some sort of "loading/authenticating" screen, however brief?)
if (success) {
[self performSegueWithIdentifier: #"MainMenuSegue" sender: self];
} else {
[self performSegueWithIdentifier: #"LoginSegue" sender: self];
}
To debug, I'd set up buttons on the initial viewcontroller just to be sure the segue linkings/etc are proper.
You really shouldn't need to instantiateViewControllerWithIdentifier unless you're working around segue/storyboard limitations. I think.
I've put the performSegueWithIdentifier in my app's first viewcontroller's viewDidAppear (not the best idea, I think; but that's sort of the soonest it should happen? and I would hedge towards saying it should be triggered somewhere in the viewcontroller stack, not from the appdelegate, but I haven't tested that).
When the app I'm working on is installed either via Ad-Hoc thru iTunes or built directly to the device, upon launching for the first time, the only view controller in my app that responds to orientation changes doesn't receive calls to shouldAutorotateToInterfaceOrientation: with a landscape argument passed in; debugging shows that it's only being called for portrait. Every subsequent launch behaves as I would expect - that is, there are calls to shouldAutorotateToInterfaceOrientation: made both with landscape and portrait arguments. This exact behavior can be seen in the iPhone simulator, on the iPhone and on the iPod touch.
So my question is: why would orientation notifications be different for the first launch of an app than they would be for every subsequent launch? Am I mistaken in believing that I have no control over orientation changes beyond responding to shouldAutorotateToInterfaceOrientation:?
Inside the ViewController in question:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
and inside of viewDidLoad and viewDidUnload I've got (respectively):
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
and
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
Update July 6, 2010:
Still no luck solving the problem. I dropped the issue for a little while and came back to it and am still seeing the problem under completely different circumstances. Anyone?
Update July 13, 2010:
From Apple's View Controller Programming Guide:
"...the window object does much of the work associated with changing the current orientation. [...] Specifically, it works with the view controller whose root view was most recently added to, or presented in, the window. In other words, the window object works only with the frontmost view controller whose view was displayed..."
I'm adding the root view controller to the window differently on the first launch compared to every subsequent launch, so I thought maybe it had something to do with this. I have yet to trace anything back to here though...just a thought.
This thing has had around 175 views at the time of this update...no one has even the most far out obscure suggestion? Come on, throw something out there. I'm willing to entertain any guesses or suggestions at this point. I don't care if it's stupidly obscure or potentially irrelevant.
Never did solve this problem - I left the company where I encountered it before I had a chance to. However, I had a pretty good lead on it by the time I left. I contacted Apple DTS about the issue and they noted that for autorotation to work properly, all ViewControllers in the view stack related to autorotation must call the super methods in the method implementations (i.e. calling [super viewDidLoad] from within the ViewController's viewDidLoad). I don't remember which methods they cited exactly, but it's probably worth a shot to ensure you're properly calling super where appropriate.
[EDIT] If someone can confirm this, I'll mark it as the accepted answer. Thanks!
also make sure you set:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return YES;}
in ALL ViewControllers.m in your app, not just one you're working on (if you have more than one). I was struggling trying to get it going for the FirstViewController, but it wouldn't work no matter what. As soon as I added the above code to all four view controllers, it started to work just fine (in all four)
I had a similar problem - the UIDevice.h header lists endGeneratingDeviceOrientationNotifications and beginGeneratingDeviceOrientationNotifications as "nestable." It turns out I had unbalanced calls to these methods.
I solved this quickly with the following change to beginGeneratingDeviceOrientationNotifications:
if (![[UIDevice currentDevice] isGeneratingDeviceOrientationNotifications])
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
What are single-state and two-stage animation for rotating an iPhone window?
This is the "error" message I get in the Debugger Console (nothing crashes):
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
I was working through the book "Beginning iPhone Development: Exploring the iPhone SDK" by Apress (Dave Mark, Jeff LaMarche) on the Swap Project.
Everything is explained in the UIViewController Class Reference. Especially check out the View Rotation section near the top.
From the reference:
Handling View Rotations
By default, the UIViewController class
displays views in portrait mode only.
To support additional orientations,
you must override the
shouldAutorotateToInterfaceOrientation:
method and return YES for any
orientations your subclass supports.
If the autoresizing properties of your
views are configured correctly, that
may be all you have to do. However,
the UIViewController class provides
additional hooks for you to implement
additional behaviors as needed.
To temporarily turn off features that
are not needed or might otherwise
cause problems during the orientation
change, you can override the
willRotateToInterfaceOrientation:duration:
method and perform the needed actions
there. You can then override the
didRotateFromInterfaceOrientation:
method and use it to reenable those
features once the orientation change
is complete.
If you want to perform custom
animations during an orientation
change, you can do so in one of two
ways. Orientation changes used to
occur in two steps, with notifications
occurring at the beginning, middle,
and end points of the rotation.
However, in iPhone OS 3.0, support was
added for performing orientation
changes in one step. Using a one-step
orientation change tends to be faster
than the older two-step process and is
generally recommended for any new
code.
To add animations for a one-step
orientation change, override the
willAnimateRotationToInterfaceOrientation:duration:
method and perform your animations
there. To use the older two-step
method, override one or both of the
willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
and
willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
methods to configure your animations
before each step. You must choose only
one technique and override just the
methods associated with that
technique. If you override either
method associated with the two-step
technique, the view controller uses
that technique by default.
I have found the culprit in my case to be the UIImagePickerController (I also do not override any rotation animation):
[self presentModalViewController:imagePicker animated:YES];
Replacing imagePicker with a generic UIViewController doesn't generate any warnings.
I changed from willAnimateFirstHalfOfRotationToInterfaceOrientation:duration: method to willAnimateRotationToInterfaceOrientation:duration: method and warning gone.
Thanks.
Ed Marty's answer is the correct one. The reason it will happen if you are not overriding any of the rotation animation is probably that you reply "YES" to shouldAutorotate.. for some view. If you do not implement rotation at all, then you should just not override the shouldAutorotate.. method. If you do override that method, then just override the single step rotation method as well and pass it along to the super.
If you're using iOS 4 and you're getting this warning, I found a way to get rid of it. In your info.plist, there is an item called "Supported interface orientations." Select which orientations your application supports and two-stage warnings will go away when bringing up the imagePicker.
#plumiscles answer didn't quite work for me - there was no item called 'Supported Interface Orientations', probably b/c it is an old project. But you can get the same effect by editing the .plist file directly and adding this:
<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationPortrait</string>
Need to add UIImagePickerController as a subview to solve this error
[self.view addSubview:picker.view];
[self presentModalViewController:picker animated:NO];
I've had this issue after creating a tabbarcontroller with no view controllers inside (no tabs), this warning disappeared once I attached at least one view controller to it.
I wasn't over riding any of those two-step functions, but I was calling my own function when I received orientationChanged notifications, and I had this line of code in it. Commenting it out got rid of the warning and allowed the auto rotate to work properly. Auto rotate still worked with this line of code until iOS 4.2, then it broke completely. Spent a lot of time looking for why the built in autoRotate stopped working in 4.2. Maybe this will help someone else.
Commented out this line to make it work:
[[UIApplication sharedApplication] setStatusBarOrientation:currentOrientation animated:YES];
I've delete from plist "Supported interface orientations" row and warning disappears.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,