Requirements: just can not automatically rotate a single page, go to horizontal screen outside always come in horizontal screen, vertical screen outside come in to keep the vertical screen.
Action: In the controller of this page and write shouldautorotate method returns NO, find work for all iphone have reached the demand, but the iPad is not working.
Note: Please see, it is a single, specific page without automatic rotation, which is to ensure the program settings page in several directions are checked, and the pull-down device along the shortcut menu to ensure open automatically rotated.
System: iOS9
I had a similar case. Note that I wasn't using navigation controller, it was a simple modal view being presented and this could affect the rotation code.
On the first view of my application I had:
-(BOOL)shouldAutorotate {
return YES;
}
On the view that I wanted to lock on a certain orientation (landscape in my case) I didn't use the shouldAutorotate but used this one:
-(UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
Related
In my iPhone app I have a view that I want to show only in portrait mode. When navigating to that view it should be automatically displayed in portrait view. When navigating away, the orientation should change back to what it was, or, if the device orientation has changed, adapt to that. I could find information on forcing an orientation and preventing auto-rotate. I could not find anything on how to change back to the correct orientation after navigating away from that view.
So my idea was to
save the initial orientation (store in currentOrientation)
subscribe to orientation change event to keep track of orientation changes while the content is locked to portrait (update currentOrientation)
when leaving the view, restore the correct orientation using the currentOrientation value.
Edit (code now removed): Apart from it not working it was a dangerous way to go as it made extensive use of unsupported APIs.
Edit:
I believe this question can now be boiled down to the following:
Is there a documented, supported way to force the interface orientation independent of the device orientation? setValue(UIInterfaceOrientation.Portrait.rawValue, forKey: "orientation") has been recommended many times on SO and elsewhere but it does indeed seem to be an unsupported hack.
Is there a documented, supported way to update the interface orientation to the device orientation? That would be needed to "recover" from the forced interface orientation in another view without having to trigger auto rotation by turning the device back and forth.
Supported are supportedInterfaceOrientations() and shouldAutorotate(). But these will only lock the interfaceOrientation after the device has been turned to that position. They do not prevent wrong initial orientation.
There are many questions similar to this one, showing that this problem setting is not uncommon, but so far no satisfactory and complete solution using supported methods.
I had a similar problem except I needed one view controller to only work in Landscape mode and another when it was in portrait. The way I achieved this was making a custom 'root' view controller. Then on the viewWillTransitionToSize method for that controller checking for orientation and non animatedly pushing the correct view controller (so it looks like a rotation to the user). And then in Interface Builder I set the view controller's orientation property explicitly instead of being inferred. You could apply this solution by having only the landscape orientation set on the restricted view controller and then on the portrait rotation doing nothing and disabling auto rotation on the restricted view controller.
Update
I haven't had the time to test any of these but these are just the ideas I used when implementing my solution for a different VC for a different orientation, some combination of the following should hopefully work I can't be a 100% certain about it cause I did this some months ago and don't exactly remember what did and didn't work.
First of all make sure that you have setup the constraints as shown in the screenshot. Mine has iPad full screen and landscape because that's what I was doing change yours to whatever you need (portrait and the size can be inferred).
Now before doing anything else I would first check to see if this solved the problem. I needed the root view controller cause I needed a different VC for portrait and and a different one for landscape. You only need to restrict it so if this works than that's perfect otherwise there are a few other things you can try as mentioned below.
Once that's setup I would first go to the view controller who you want to restrict's class and prevent autorotation using:
override func shouldAutorotate() -> Bool {
return false
}
Now if you do that since you are restricting to portrait I'm guessing you don't really care about upside down so you don't need to do anything additional. If you do want to use the viewWillTransitionToSize method and rotate manually.
If things still don't work you can finally try the root controller way (but I would use this in the last case). Heres a sketch of it:
class VC : UIViewController {
override func viewDidLoad () {
UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: "UIDeviceOrientationDidChangeNotification", object: nil)
// this gives you access to notifications about rotations
}
func orientationChanged(sender: NSNotification)
{
// Here check the orientation using this:
if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) { // Landscape }
if UIInterfaceOrientationIsPortrait(UIApplication.sharedApplication().statusBarOrientation) { // Portrait }
// Now once only allow the portrait one to go in that conditional part of the view. If you're using a navigation controller push the vc otherwise just use presentViewController:animated:
}
}
I used the different paths for the if statements to push the one I wanted accordingly but you can do just push the portrait one manually for both and hopefully one of the ways above will help you.
I've noticed that most consumer-friendly Android and iPhone fitness apps have two interface modes - in portrait mode the user gets more detailed information, but when the user turns the device to landscape mode, a full screen graph is added to cover the entire screen.
I'm interested in how to implement transition to a different view controller in response to device rotation on iPhone. My initial thoughts are to intercept (willRotateToInterfaceOrientation event, then get the app delegate and add a full screen graph view controller to the window).
Is there a better way of turning an iPhone rotation into a transition to another view controller? Like hiding the status bar and pushing a modal view controller in landscape mode with animation?
First ask yourself whether you really need a separate view controller. One view controller can easily hide or unhide a graph.
If this graph needs its own view conroller then you could use a container view that contains the graph which refers to its own view conroller. That is what container views are made for.
The "Master" view controller then would just hide and unhide the container view in response to rotation events (and layout them accordingly etc. pp.)
If you prefer to add or remove the container view from its super view (most probably self.view from the "Master" view controller's point of view) then do that instead of hiding and unhiding. That is probably most appropriate.
The upside of this appoach would be that it works regardless of the navigaiton structure you are in, regardless of whether the rotated view controller was pushed or presented modally, regardless of whether you are in a tab bar driven app or a single view app, whether you are using storyboard, works with IB as well as programmatically, etc. pp.
There is nothing wrong with fetching the window instance from the app's delegate. I just don't see the need for doing so. Seems rather complicated to me compared to the alternatives.
The willRotateToInterfaceOrientation method works well.
In addition to switching views, two other useful things you might want to do in there are:
1) Hide/Show the status bar. (I like to hide it in landscape)
[[UIApplication sharedApplication] setStatusBarHidden:UIInterfaceOrientationIsLandscape(toInterfaceOrientation) withAnimation:UIStatusBarAnimationSlide];
2) Hide/Show any UINavigationBar. (Maybe your landscape view will benefit from the extra height)
[self.navigationController setNavigationBarHidden:UIInterfaceOrientationIsLandscape(toInterfaceOrientation) animated:YES];
You could have one view controller that has the willRotateToInterfaceOrientation method, and that viewcontroller has two other viewcontrollers as variables.
Once the device rotates, you switch the viewcontrollers' views (very crude code example:)
-(void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight)) {
[self.secondViewController.view removeFromSuperView];
self.firstViewController.view.frame = self.bounds;
[self.view addSubView:self.firstViewController.view];
} else {
[self.firstViewController.view removeFromSuperView];
self.secondViewController.view.frame = self.bounds;
[self.view addSubView:self.secondViewController.view];
}
}
Okay so I'm new to the whole storyboarding functionality in iOS 5. I have an application for iPad that is locked into landscape mode. Just to test out the seguing functionality, I took my initial view controller, added a button, dragged a new view controller next to it (which shows up in landscape mode visually on the designer), then tied the segue action to the button. I left everything defaulted.
Hitting the button on the initial view controller initiates the segue with no problems, and the new screen loads up, but ALWAYS in portrait mode.
What am I doing wrong? Is there some toggle I'm missing? I figured that if via the summary of the project, I have it locked into landscape left and right, it would assume I always want that orientation unless otherwise noted? Please help!
I had the same problem and managed it by adding a new own ViewControllerClass to the new scene.
Within the following auto created method, you can restrict the orientation to landscape only. This is also helpful for the Main Scene ViewController:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (UIInterfaceOrientationIsLandscape(interfaceOrientation));
}
Cheers,
Daniel
I have an application for iPad that is locked into landscape mode.
How are you locking it app-wide? Just want to make sure you are doing it correctly.
To lock an orientation in Storyboard mode, select your ViewController and on the Attributes inspector change the Orientation from Inferred to whatever you want it to be locked to.
What have you put in the orientation delegate method?
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
In the properties file for your app (YOURAPPNAME-Info.plist), located in the "supporting files" group, there is an array called "Supported interface orientations". Remove both landscape values from the array and your app will be locked in portrait orientation.
I'm doing some fancy stuff with rotation, and am returning NO from shouldAutorotateToInterfaceOrientation:. I then manually rotate a UIWebView according to [UIDevice currentDevice].orientationCached. When I hold my finger down on a link, the menu which appears is always in PortraitLeft orientation, no matter how the device is actually oriented, and no matter how the UIWebView is oriented.
It looks to me like the link menu takes its orientation from the main view of the view controller, rather than the UIWebView its associated with, and that the only way to get it to behave correctly is to return YES from -shouldAutorotateToInterfaceOrientation:.
Is this assumption correct? Is there any way to control the orientation of the link-related popup menu, or to force it to take its orientation from the UIWebView which spawns it?
I wouldn't actually set the orientation manually how you're doing it in the UIWebView. Instead, force the orientation of the parent view controller through code.
As an example of a similar situation, I have an app that I've developed that displays a map in a few views. When rotated from portrait to landscape, it layouts the view in a very different manner from the portrait layout with a fancy animation. I like it, but some users don't, so I provide the option to disable map rotation. And I do that by passing in only certain rotation abilities to the parent view. So if they disable landscape, I tell the parent view controller it can only rotate to landscape. Otherwise, it's free to rotate to any orientation except upside down.
Enough with the explanation: here's my relevant code I use to accomplish this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
//Rotate the view if rotation is actually enabled
if ([self.prefs boolForKey:#"SHOULD_ROTATE"]) {
return (UIInterfaceOrientationIsLandscape(toInterfaceOrientation) || UIInterfaceOrientationPortrait == toInterfaceOrientation);
}
//Rotate it to portrait only if rotation is disabled
else if (![self.prefs boolForKey:#"SHOULD_ROTATE"]) {
return (UIInterfaceOrientationPortrait == toInterfaceOrientation);
}
//Otherwise, rotate only to portrait (for all views minus the map views)
else {
return (UIInterfaceOrientationPortrait == toInterfaceOrientation);
}
}
I actually implement this in the UITabBarController so that it applies to all views and view controllers in the app, but it's just as easily implemented only for the UIWebView parent view controller. The gist is that you're returning yes or no based on if the view orientation matches what you want it to. For the web view, you would want to lay out the only allowed rotation orientations to by returning this:
return (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)
Of course, I haven't discussed forcing the view to set the view to a landscape view, only what it does once a user turns it. So the best course of action there is simply to manually do it once the view is initialized:
[[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeLeft animated:NO]
You'll have to set it to either Left or Right to start with, but the user can rotate between right and left as they wish if you implement my earlier code as well.
That's a lot of explanation, but I think the combination of these two methods for the parent view controller should allow you to present the view exactly how you wish. Let me know if you have any questions!
By "popup menu", do you mean the Action Sheet with Open … Copy buttons?
If so, then yes, Action Sheet always presents itself according to the top view controller's interfaceOrientation. Because you are rotating the view yourself without its controller updating its interfaceOrientation, the Action Sheet still thinks it is portrait.
As has been reported in other questions here on SO, iOS 5 changes how rotation callbacks for split view controllers are sent as per this release note. This is not a dupe (I think), as I can't find another question on SO that deals with how to adjust split view controller usage in iOS 5 to cope with the change:
Rotation callbacks in iOS 5 are not applied to view controllers that
are presented over a full screen. What this means is that if your code
presents a view controller over another view controller, and then the
user subsequently rotates the device to a different orientation, upon
dismissal, the underlying controller (i.e. presenting controller) will
not receive any rotation callbacks. Note however that the presenting
controller will receive a viewWillLayoutSubviews call when it is
redisplayed, and the interfaceOrientation property can be queried from
this method and used to lay out the controller correctly.
I'm having trouble configuring the popover button in my root split view controller (the one that is supposed to show the left pane view in a popover when you're in portrait). Here's how my app startup sequence used to work in iOS 4.x when the device is in landscape mode:
Install split view controller into window with [window addSubview:splitViewController.view]; [window makeKeyAndVisible];. This results in splitViewController:willHideViewController:withBarButtonItem:forPopoverController: being called on the delegate (i.e. simulating a landscape -> portrait rotation) even though the device is already in landscape mode.
Present a fullscreen modal (my loading screen) which completely covers the split view underneath.
Finish loading and dismiss the loading screen modal. Since the device is in landscape mode, as the split view controller is revealed, this causes splitViewController:willShowViewController:invalidatingBarButtonItem: to be called on the delegate (i.e. simulating a portrait -> landscape rotation), thereby invalidating the bar button item, removing it from the right-side of the split view, and leaving us where we want to be. Hooray!
So, the problem is that because of the change described in that release note, whatever happens internally in iOS 4.3 that results in splitViewController:willShowViewController:invalidatingBarButtonItem: being called no longer happens in iOS 5. I tried subclassing UISplitViewController so I could provide a custom implementation of viewWillLayoutSubviews as suggested by the release note, but I don't know how to reproduce the desired sequence of internal events that iOS 4 triggers. I tried this:
- (void) viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
UINavigationController *rightStack = [[self viewControllers] objectAtIndex:1];
UIViewController *rightRoot = [[rightStack viewControllers] objectAtIndex:0];
BOOL rightRootHasButton = ... // determine if bar button item for portrait mode is there
// iOS 4 never goes inside this 'if' branch
if (UIInterfaceOrientationIsLandscape( [self interfaceOrientation] ) &&
rightRootHasButton)
{
// Manually invoke the delegate method to hide the popover bar button item
[self.delegate splitViewController:self
willShowViewController:[[self viewControllers] objectAtIndex:0]
invalidatingBarButtonItem:rightRoot.navigationItem.leftBarButtonItem];
}
}
This mostly works, but not 100%. The problem is that invoking the delegate method yourself doesn't actually invalidate the bar button item, so the first time you rotate to portrait, the system thinks the bar button item is still installed properly and doesn't try to reinstall it. It's only after you rotate again to landscape and then back to portrait has the system got back into the right state and will actually install the popover bar button item in portrait mode.
Based on this question, I also tried invoking all the rotation callbacks manually instead of firing the delegate method, e.g.:
// iOS 4 never goes inside this 'if' branch
if (UIInterfaceOrientationIsLandscape( [self interfaceOrientation] ) &&
rightRootHasButton)
{
[self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
[self willAnimateRotationToInterfaceOrientation:self.interfaceOrientation duration:0];
[self didRotateFromInterfaceOrientation:self.interfaceOrientation];
}
However this just seems to cause an infinite loop back into viewWillLayoutSubviews :(
Does anyone know what the correct way to simulate the iOS4-style rotation events is for a split view controller that appears from behind a full-screen modal? Or should you not simulate them at all and is there another best-practices approach that has become the standard for iOS5?
Any help really appreciated as this issue is holding us up from submitting our iOS5 bugfix release to the App Store.
I don't know the right way to handle this situation. However, the following seems to be working for me in iOS 5.
In splitViewController:willHideViewController:withBarButtonItem:forPopoverController:, store a reference to the barButtonItem in something like self.barButtonItem. Move the code for showing the button into a separate method, say ShowRootPopoverButtonItem.
In splitViewController:willShowViewController:invalidatingBarButtonItem:, clear that self.barButtonItem reference out. Move the code for showing the button into a separate method, say InvalidateRootPopoverButtonItem.
In viewWillLayoutSubviews, manually show or hide the button, depending on the interface orientation
Here's my implementation of viewWillLayoutSubviews. Note that calling self.interfaceOrientation always returned portrait, hence my use of statusBarOrientation.
- (void)viewWillLayoutSubviews
{
if (UIInterfaceOrientationIsPortrait(
[UIApplication sharedApplication].statusBarOrientation))
{
[self ShowRootPopoverButtonItem:self.barButtonItem];
}
else
{
[self InvalidateRootPopoverButtonItem:self.barButtonItem];
}
}