I use a tabBar Controller as root controller. It has 4 tabs and each of its ViewControllers has
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
as well as the tabBarController itself.
But when I rotate the device (real or simulator), the screen turns at random! If it doesn't turn when I open the application it would have the same behavior until I quit the app.
I tried to add the 4 viewControllers one by one in IB to see if one was problematic, but I obtained the same issue. It only always turns when there is no tabs at all!
Please tell me if you have any ideas. Thanks!
You set every view controller to say that it responds to any possible orientation. Therefore, every view will attempt to rotate to every orientation.
Views don't really automatically rotate. You usually have to manage the placement of subview programmatically in all but the simplest views.
If you have no custom orientation code, you're probably seeing the views try to draw the portrait view in the landscape frame or vice versa. If you have autoresize subviews set your subviews will appear to scatter across the screen in a seemingly random pattern. The more you change orientation, the more random the placement becomes.
For complex views, I like to create separate viewController/view pairs for each orientation. Then I put the views in a nav controller. As the orientation changes, each view controller will push or pop the appropriate view controller for the coming orientation onto/off the stack. To the user, this looks like a single view is gracefully redrawing itself. (This is especially useful if you have non-standard UI elements that have to be manually rotated with transforms)
You have to subclass UITabBarController and implement shouldAutorotateToInterfaceOrientation:
Actually, I just want my first tab view controller to rotate. So I put this code in my custom tabBarController :
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
if (self.selectedIndex == 0) {
return toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}else {
return toInterfaceOrientation == UIInterfaceOrientationPortrait;
}
}
but I had the same problem. I use a custom orientation code for my first tab view controller when turning to landscape. Called with the following function in my custom tabBarcontroller:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationPortrait) {
//rotation to Portrait
lastOrientation = toInterfaceOrientation;
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
[self.selectedViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
else if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
if (!UIInterfaceOrientationIsLandscape(lastOrientation)) {
//rotation to Landscape
[self.selectedViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
lastOrientation = toInterfaceOrientation;
}
}
I found that if you set the selected tab programmatically the tabViewController rotates erratically.
Related
I have one app always presenting in Portrait mode (in the summary of the Xcode project, only the portrait orientation is supported).
Now what I want to do is when I'm using the app, from any view controllers of the app, if I rotate the device in landscape right, the app presents a view controller (ARViewController.m) in landscape right, in fact the rotation to landscape right is the trigger to present ARViewController.m. But what I've experienced is, since the the first view controller only supports portrait and even if I orient the device in landscape right, the view controller (ARViewController.m) I want to present from the first one is in portrait too, not in landscape right.
Even if I write this in the second view controller (ARViewController.m), it doesn't autorotate (this view controller can be presented in every orientations):
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return YES;
}
I have to rotate the iphone once after the second view controller (ARViewController.m) is presented to have all in order.
And here is how I call this second view controller (ARViewController.m) from the first view controller:
ARViewController *arVC = [[ARViewController alloc] initWithNibName:#"ARViewController" bundle:nil];
[self presentModalViewController:arVC animated:YES];
I'm calling it from "ViewController.m", this one is defined as the rootViewController in the AppDelegate.m.
This is the first time I'm doing such things, I've looked for solutions but still the same problem. Any tips on this?
I finally solved this problem, I suppose there are alternatives but this one works fine:
In fact I kept only Portrait in the orientation restrictions. Then when I turn the phone in landscape right or left, I call my ARViewController modally, but before loading it I force this view controller to landscape (in viewWillAppear) by making an appropriate rotation like here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self transformView2ToLandscape];}
-(void) transformView2ToLandscape {
NSInteger rotationDirection;
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
if(currentOrientation == UIDeviceOrientationLandscapeLeft){
rotationDirection = 1;
}else {
rotationDirection = -1;
}
CGAffineTransform transform = [arController.viewController.view transform];
transform = CGAffineTransformRotate(transform, degreesToRadians(rotationDirection * 90));
[arController.viewController.view setTransform: transform];}
Edit: In Swift 4
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
transformViewToLansdcape()
}
func transformViewToLansdcape(){
var rotationDir : Int
if(UIDeviceOrientationIsLandscape(UIDevice.current.orientation)){
rotationDir = 1
}else{
rotationDir = -1
}
var transform = self.view.transform
//90 for landscapeLeft and 270 for landscapeRight
transform = transform.rotated(by: (rotationDir*270).degreesToRadians)
self.view.transform = transform
}
extension BinaryInteger {
var degreesToRadians: CGFloat {
return CGFloat(Int(self)) * .pi / 180
}
}
What i've found is that first of all, what's stopping the entire application from rotating is the project summary sheet. What you should do is deselect the restrictions in the project summary sheet & just put a method in each UIViewController like you'd like it to be. In the UIViewController that you have, make the method available for the landscape orientations & implement the method
[UIViewControllerSubclass willRotateToInterfaceOrientation:<interfaceOrientation> duration:<NSTimeInterval>]
what Apple says:
willRotateToInterfaceOrientation:duration:
Sent to the view controller just before the user interface begins rotating.
(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Parameters
toInterfaceOrientation
The new orientation for the user interface. The possible values are described in
UIInterfaceOrientation.
duration
The duration of the pending rotation, measured in seconds.
Discussion
Subclasses may override this method to perform additional actions immediately prior to the rotation. For example, you might use this method to disable view interactions, stop media playback, or temporarily turn off expensive drawing or live updates. You might also use it to swap the current view for one that reflects the new interface orientation. When this method is called, the interfaceOrientation property still contains the view’s original orientation.
This method is called regardless of whether your code performs one-step or two-step rotations.
so, this seems like the method you are looking for. Just implement this, and put your view calling code inside & it should work. (also, put one of these in the presented view to return when rotated back)
also, you should consider not presenting the view controller modally as it is rotating anyways and is obvious that the display is changing.
I have a tab bar application where everything is working fine. I have rotations of the device all working fine with the various Tab Bar View controllers.
Alas it was suggested that a couple of the View Controllers needed a help page. To this end I created a new ViewController that contains a UIWebView (where help can be built into an HTML file).
I create the new "HelpViewController" as follows:
mpHelpPage = [[HelpPageViewController alloc] init];
[mTabBarController.view addSubview: mpHelpPage.view];
[mpHelpPage retain];
mpHelpPage.view.alpha = 0.75f;
This brings up the help page no problems when I'm in portait mode. Unfortunately when I'm in landscape mode and I do the above code it adds the HelpViewController in Portrait (meaning it extends off the bottom of the screen).
As such when I alloc the ViewController above I need some way of telling the ViewController to rotate to the current device orientation.
I am, however, at a loss as to how to get it to do this. Any help would be much appreciated!
I handle this annoyance by putting an orientation check in viewWillAppear:, e.g.
if (self.interfaceOrientation == UIInterfaceOrientationPortrait ||
self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
// custom code or call willRotate
} else {
// custom code or call willRotate
}
You can also do this if you prefer
if (UIDeviceOrientationIsPortrait(self.interfaceOrientation)) {
// custom code or call willRotate
} else {
// custom code or call willRotate
}
you should either set the frame-property of your subview in willRotateToInterfaceOrientation:duration: of your ViewController
or you write your own View and set the frame-property in layoutSubviews of your View
The added Subview should handle the layout of its subviews.
Since you've added HelpViewController as a subview and no UIViewController controls it, it will not be resized. You can resize HelpViewController's view manually by detecting a change in the orientation in the shouldAutorotateToInterfaceOrientation: method of the current UIViewController. This method passes the current orientation as its argument, so just check which is the current orientation and set a frame accordingly as:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if((interfaceOrientation==UIInterfaceOrientationLandscapeLeft) || (interfaceOrientation==UIInterfaceOrientationLandscapeRight))
mpHelpPage.view.frame = CGRectMake(0,0,480,300);
else
mpHelpPage.view.frame = CGRectMake(0,0,320,460);
return YES;
}
Or, Instead of adding HelpViewControlleras a subView, try [self.navigationController pushViewController:HelpViewController animated:YES];
I have two views in my App and i am having problem loading my second view when i rotate my emulator.
My first view redirects my app to second view on a button event.
When i try to rotate each view independently, it works perfect. But if i load my first view and rotate the emulator then try to load another view i am getting this :-
second view loaded as portrait where my emulator's orientation is landscape.
But if i load my second view before rotating the emulator and then rotate it, it gets loaded perfect.
Do i need to resize my view or need to call any function to make it load properly?
i am a newbie in iOS world so any help would be highly appreciated.
Thanks.
Make sure that in your second view you use this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight || interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
Add new view to UINavigationController using following code:
- (IBAction)changeView:(id)sender
{
RedClass *redClass = [[RedClass alloc] initWithNibName:#"RedClass" bundle:nil];
[self.navigationController pushViewController:redClass animated:YES];
[redClass release];
}
If you cal that method then your view will autoresize.
When you just adding to subview using method self.view addSubview:redClass.view]; it will autoresize if only current view will be changed after adding.
I have a background image in my main window so that when I flip views, it's not a blank white screen behind, but an image. My problem is that this image doesn't rotate when the device rotates.
Edit: As far as I can tell, Brett was correct when he pointed out that I'd have to rotate the background image manually in this instance. In case it helps anyone else out in the future, here's how I rotated it.
Inside myAppDelegate:
- (void) application:(UIApplication *)application
willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation
duration:(NSTimeInterval)duration
{
if (newStatusBarOrientation == UIInterfaceOrientationPortrait)
self.bgImage.transform = CGAffineTransformIdentity;
else if (newStatusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)
self.bgImage.transform = CGAffineTransformMakeRotation(-M_PI);
else if (UIInterfaceOrientationIsLandscape(newStatusBarOrientation))
{
float rotate = ((newStatusBarOrientation == UIInterfaceOrientationLandscapeLeft) ? -1:1) * (M_PI / 2.0);
self.bgImage.transform = CGAffineTransformMakeRotation(rotate);
self.bgImage.transform = CGAffineTransformTranslate(self.bgImage.transform, 0, -self.bgImage.frame.origin.y);
}
}
I think that UIWindow passes on rotation events to child controllers. The window should contain the root view controller view then pass on the rotation event message to the view controller to manage the rotation.
You should be able to listen for these events in your app delegate and manage the image rotation manually. Otherwise, just add the image as a subview of the root view controller.
On your rootViewController make sure you implement this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
You rootViewController will probably just be a simple UIVIew with 2 UIViews, one for the background image view and the other for your main interface view.
I'm having the landscape mode issue and I can not find the way out. Basically, I'm having a tab bar application and in the first tab i have navigation controller. In this navigation controller, first view contains table with items and after clicking the item, detail view describing the item is pushed.
I need to implement landscape mode for both list and detail view, but for list view, i need to use different view controller for landscape mode (generally, something like cover flow). Detail view is just changing orientation and no need to use alternate view controller in this case.
I tried to achieve this behaviour by implementing modal view controller for list view controller, according to Alternate Views example by Apple. This works fine when I'm in list view (when I turn device into landscape mode, cover flow view controller is correctly presented). Problem comes when I'm showing detail view. When I change the device orientation, cover flow shows up again. What I expected is that cover flow will be presented only in case that list view is on the screen. It seems like modal view controller is always visible no matter what VC is currently on the stack of NC.
It seems to me that presenting modal VC as landscape view for particular VC is not working for multiple navigation levels.
I also tried to add landscape view as a subview into view controllers view. When using this solution, i have no problem with navigation levels, but issue here is that tab bar is not hidden in landscape mode. I need to hide tab bar for cover flow, which is achieved by presenting modal VC.
I will appreciate any help with this issue.
Great thanks!
In the detail view controller, you could set up a different view entirely using something like this (code from a recent project of mine):
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toOrientation
duration:(NSTimeInterval)duration
{
if ([graphView superview]) {
if (toOrientation == UIInterfaceOrientationPortrait ||
toOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[graphView removeFromSuperview];
}
} else {
if (toOrientation == UIInterfaceOrientationLandscapeLeft ||
toOrientation == UIInterfaceOrientationLandscapeRight) {
[[self view] endEditing:YES];
[[self view] addSubview:graphView];
}
}
}
And now to hide the tabbar when you are in landscape (bit of a hack, but works):
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation toOrientation = self.interfaceOrientation;
if ( self.tabBarController.view.subviews.count >= 2 )
{
UIView *transView = [self.tabBarController.view.subviews objectAtIndex:0];
UIView *tabBar = [self.tabBarController.view.subviews objectAtIndex:1];
if(toOrientation == UIInterfaceOrientationLandscapeLeft ||
toOrientation == UIInterfaceOrientationLandscapeRight) {
transView.frame = CGRectMake(0, 0, 480, 320 );
tabBar.hidden = TRUE;
}
else
{
transView.frame = CGRectMake(0, 0, 320, 480);
tabBar.hidden = FALSE;
}
}
}
For this project, I added a view called "graphView" that I wanted to appear if and only if in landscape mode, and then I wanted to tabbar to be hidden. This sounds similar to what you're after, I think.
The only potential problem I foresee is that if you enter landscape mode before the detail view is pushed, things could get wonky. Therefore you may want to use these methods in the list view controller instead. This particular problem never arose for me, but it's something I thought about before I realized it was moot.