I would like to implement the following. When the user rotates the iPhone, I want to instantiate a new UIViewController (automatically upon rotation, not clicking a button or performing a similar action) and show to the user a view handled by this new UIViewController in landscape orientation. How to do this properly ?
I tried to instantiate the new controller in the methods willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation, but none of this methods gets called!. I suspect this is because the current controller is pushed in by a navigation controller which is itself handled by a tabBarController. Any clue? A simple code snippet would be greatly appreciated.
Thank you in advance.
A cleaner way is NOT to use UIInterfaceOrientation for the rotation.
Why, because then your interface or view 1 actually rotates. This means you have to rotate it back to redisplay it after view 2 is removed.
To compensate for this, I simple subscribe to UIDeviceOrientation notifications. Subtly different. View 1 does NOT have to support autorotation to make this method work. In viewDidLoad enter the following code:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(detectOrientation) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
Then just define the method detectOrientation:
-(void) detectOrientation {
if (([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft) ||
([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight)) {
//load view 2
} else if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) {
// load view 1
}
}
Now neither of your views need support autoratation! You will however need to perform a transform on view 2 before loading:
-(void) transformView2ToLandscape {
NSInteger rotationDirection;
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
if(currentOrientation == UIDeviceOrientationLandscapeLeft){
rotationDirection = 1;
}else {
rotationDirection = -1;
}
CGRect myFrame = CGRectMake(0, 0, 480, 300);
CGAffineTransform transform = [[self view2] transform];
transform = CGAffineTransformRotate(transform, degreesToRadians(rotationDirection * 90));
[[self view2] setFrame: myFrame];
CGPoint center = CGPointMake(myFrame.size.height/2.0, myFrame.size.width/2.0);
[[self view2] setTransform: transform];
[[self view2] setCenter: center];
}
Thats how I swap views o rotation without supporting autorotation in my views.
I've implemented this exact type of behavior on an app and the key is to make sure that any parent controllers implement shouldAutorotateToInterfaceOrientation and that your current view controller also implements it. In my case I am using a tab controller which intercepts the call to shouldAutorotateToInterfaceOrientation and I had to override the tab controller to get the call to fall through. The default behavior of view controllers is to display in portrait mode only.
So you need to force them to allow all orientation changes through:
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Then in order to load a new view controller you should respond to:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait) ||
(fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown))
{
// Load the view controller you want to display in landscape mode...
}
}
and can also use:
-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
To detect that the orientation change is coming.
In my case I am using didRotateFromInterfaceOrientation to check if the last orientation was portrait and if so load the view I want displayed in landscape mode.
I then implemented the same methods in the viewcontroller I am loading and it is responsible for detecting when the orientation changes back to portrait and dismissing itself from the view stack.
Hope that helps a little.
Paul
Indeed only top-level controller is notified. You're responsible for notifying nested controllers.
Add this to your tab bar controller:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[self.selectedViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
Do you implement shouldAutorotateToInterfaceOrientation? If not, then that might be why you aren't getting the messages.
Related
I know similar questions have been asked before, but this is a more specific use case (controlling orientation from a static library, cannot write in root view controller).
I have a static library which adds UI elements as overlays to a passed view controller (the client's root view controller) as subviews. Problem is our UI elements support portrait orientation only, while our client's application may support both portrait and landscape. This is fine, as long as our UI elements don't autorotate when our client's views do.
I'd like to lock the orientation to portrait only for our view controller only. In iOS 6, when I use the following code in my library's view controller, it doesn't affect the behaviour of autorotate at all:
-(BOOL)shouldAutorotate{
return NO;
}
-(NSInteger)supportedInterfaceOrientations{
NSInteger orientationMask = 0;
if ([self shouldAutorotateToInterfaceOrientation: UIInterfaceOrientationPortrait])
orientationMask |= UIInterfaceOrientationMaskPortrait;
return orientationMask;
}
When I put the same code in the root view controller, it works perfectly, with the app no longer auto-rotating. However, this is not an option for us since in production we will not have access to our client's root view controller. Is there a way to either lock view orientation from NOT a root view controller, or lock orientation for a single view controller only? Any other way of achieving what we need that I'm not thinking of? Hoping for solutions that work in iOS <= 6 if at all possible
You can't lock orientation only for one view controller in whole application . So you have to set transform angle with parent view frame on view launch and on change of orientation.
You add code for those view controller class
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
[[self view] setBounds:CGRectMake(0, 0, self.view.frame.size.height, self.view.frame.size.width)];
CGFloat radians = atan2f(self.view.transform.b, self.view.transform.a);
if (radians!=M_PI_2) {
[[self view] setTransform:CGAffineTransformMakeRotation(M_PI_2)];
}
}
}
(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
[[self view] setBounds:CGRectMake(0, 0, self.view.frame.size.height, self.view.frame.size.width)];
CGFloat radians = atan2f(self.view.transform.b, self.view.transform.a);
if (radians!=M_PI_2) {
[[self view] setTransform:CGAffineTransformMakeRotation(M_PI_2)];
}
}else{
[[self view] setBounds:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGFloat radians = atan2f(self.view.transform.b, self.view.transform.a);
if (radians!=0) {
[[self view] setTransform:CGAffineTransformMakeRotation(0)];
}
}
}
I have created an application to support all orientations.
Also I have created a table and search bar in viewDidLoad method (without using xib). I have also written following code to adjust the positioning of table view and search bar.
It works properly when I launch my application in portrait view and when I rotate to landscape view, it adjust the views perfectly.
But when I launch my application in landscape mode, views are not adjusted, instead they take positioning of portrait view. But again when I rotate in portrait and then landscape it works all right.
Also to ensure that correct orientation is captured, I have written NSLog inside this method, and it shows me the correct value.
Also to ensure that this method (adjustViewsForOrientation) is called explicitly, I have called the method in viewDidLoad as well.
[self adjustViewsForOrientation:self.interfaceOrientation];
Here is a rest of code snippet:
- (void) adjustViewsForOrientation:(UIInterfaceOrientation)orientation {
if (orientation == UIInterfaceOrientationLandscapeLeft ||
orientation == UIInterfaceOrientationLandscapeRight) {
aTableView.frame = CGRectMake(705, 220, 320, 280);
sBar.frame=CGRectMake(805, 0, 220, 40);
NSLog(#"inside landscape");
}
else if (orientation == UIInterfaceOrientationPortrait ||
orientation == UIInterfaceOrientationPortraitUpsideDown) {
aTableView.frame = CGRectMake(450, 220, 320, 280);
sBar.frame=CGRectMake(550,3,220,40);
}
}
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
// [self adjustViewsForOrientation]
[self adjustViewsForOrientation:toInterfaceOrientation];
NSLog(#"landscap");
}
Try this approach, it may help. Register for UIDeviceOrientationDidChange in viewDidLoad method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(adjustViewsForOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
and also change -(void) adjustViewsForOrientation:(UIInterfaceOrientation)orientation
to -(void) adjustViewsForOrientation:(NSNotification *)notification
and obtain the new orientation by getting the orientation value
e.g. UIDeviceOrientation newDeviceOrientation = [notification orientation];
How can I write my code which supports both landscape and portrait? I tried this one -
(void)setupForOrientation:(UIInterfaceOrientation)orientation {
if (UIInterfaceOrientationIsPortrait(orientation)) {
CGRect bounds = CGRectMake(0, 0, 768, 1004);
scrollView.frame = bounds;
// Other view positioning for portrait.
} else {
CGRect bounds = CGRectMake(0, 0, 1024, 748);
scrollView.frame = bounds;
// Other view positioning for landscape.
}
[self drawBackgroundForOrientation:orientation];
}
But its not working. Please help me.
Use this code:
- (void)viewDidLoad
{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
}
- (void) orientationChanged:(id)object
{
if( [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft || [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight )
{
self.view=self.landscapeView;
NSLog(#"ViewwillAppear= Land");
}
else
{
self.view = self.portraitView;
NSLog(#"ViewwillAppear= Port");
}
You'll always have to return yes for this function whenever you require orientation support.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
Edit 1: P.S. I've taken portraitView & landscapeView as two separate UIViews.
I think you want to override the shouldAutorotateToInterfaceOrientation: method of your main view controller to always return YES. That or you can specify the supported orientations for your app in the app properties.
You have to use following method to support both orientation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
//this method will call just before changing Orientation of device you can set frames in this method
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
}
If you have used viewcontrollers then there are a few methods that you can override. The first being
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; // Override to allow rotation. Default returns YES only for UIInterfaceOrientationPortrait
Where you can specify whether a particular view will support rotation or not.
If you need to configure your views manually according to the orientation you can override the following methods
As per the documentation
Your implementation of this method should simply return YES or NO based on the value in the interfaceOrientation parameter. Do not attempt to get the value of the interfaceOrientation property or check the orientation value reported by the UIDevice class. Your view controller is either capable of supporting a given orientation or it is not.
// Notifies when rotation begins, reaches halfway point and ends.
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
AND
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
To achieve what you wish to implement you can return YES for supported orientation and then use the second method to configure your view accordingly by calling [self setupForOrientation:orientation];
Hope it helps!!!
I am building an application with multiple UIViewControllers controlled by a RootViewController. Currently in the plist the application defaults to LandscapeRight.
Lets say I have the following files that can be loaded into the RootViewController:
IntroView (Landscape Right ONLY)
LandscapeView (Landscape Right ONLY)
PortraitView (Portrait ONLY)
I also added into the RootViewController's shouldAutorotateToInterfaceOrientation the following:
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if ([currentClass class] == PortraitView.class) {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
} else {
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
}
so all is well until I load in the PortraitView and in the viewWillAppear i add the following (similar to this thread
if (self.interfaceOrientation == UIInterfaceOrientationPortrait) { //UIInterfaceOrientationLandscapeRight) {
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
self.view.bounds = CGRectMake(0.0, 0.0, 320, 480);
self.view.center = CGPointMake(240.0f, 160.0f);
}
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
This yields a properly loaded in PortraitView but now my issue is that within my PortraitView I have 2 subviews that I would like to flip between, but because I rotated my view UIViewAnimationTransitionFlipFromLeft actually makes it flip top to bottom instead of left to right. I cannot for the life of me figure out how to tell PortraitView that its in Portrait.
When I check [[UIDevice currentDevice] orientation]; it tells me its in Landscape. When I attempt to use shouldAutorotateToInterfaceOrientation and set it to Portrait only it will then rotate my view so it is no longer correct.
Hopefully this makes sense! Been looking at this issue all day.
Thanks!
I ran into this slightly differently; a UIViewController subclass in landscape mode that swapped top-to-bottom instead of left-to-right.
The fix that worked for me was to add all my views to a single “inner” view and then flip that instead of the UIViewController’s normal view.
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight
forView:[self innerView]
cache:YES];
Where [self innerView] is the sole child of [self view], and all the subviews are inside of it.
I have a simple UIViewController that uses a XIB for its interface.
As such, the interface simply comprises a UIView, a UIActivityIndicator and a UILabel. I have the sizing constraints in Interface Builder set to keep the activity indicator and the label centred when the view rotates.
The UIViewController is set to return YES for portrait and landscape orientations in the -shouldAutorotateToInterfaceOrientation: method.
The view is added to the view hierarchy manually, using
if (!activityOverlay)
activityOverlay = [[XBActivityOverlayViewController alloc] initWithNibName:#"XBActivityOverlayViewController" bundle:nil message:#"Connecting..."];
[activityOverlay.view setAlpha:0.0f];
[self.window addSubview:activityOverlay.view];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3f];
[activityOverlay.view setAlpha:0.9f];
[UIView commitAnimations];
The problem I'm having is that if the device is already in landscape orientation and the view is added to the hierarchy at this point, the view is still in portrait orientation, and doesn't auto rotate.
What do I need to do to add the view in the same orientation as the parent view?
I do not know if it is the right answer to your question but I hope can help:
For me, every time I add/dismiss a modal or display a new view, the following function is called:
-(void) detectOrientation
{
if (([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft) ||
([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight))
{
item1.frame = CGRectMake(147.0, 241.0, 56.0, 28.0);
item2.frame = CGRectMake(265.0, 241.0, 56.0, 28.0);
}
else if (([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) ||
([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown) || !inLandscapeOrientation)
{
item1.frame = CGRectMake(35.0, 425.0, 35.0, 28.0);
item2.frame = CGRectMake(176.0, 425.0, 35.0, 28.0);
}
}
Here I change the position according to the current device orientation. Maybe if you detect that the current orientation is landscape you add a subview that has such orientation.
Alejandra :)