I have a view controller with a paging scrollview displaying pages of data to the user. I'm having trouble getting the scroll view to "snap to" pages when the user rotates the device while in the middle of scolling to the next/previous page.
I notice in the native photo viewer app that rotation is essentially disabled during scrolling. I'm trying to mimic this behaviour.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (isScrolling)
return interfaceOrientation == self.interfaceOrientation;
return YES;
}
The problem is I need some way to tell the runtime that I am ready to change my orientation. Something like setNeedsLayout but for View Controllers.
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
isScrolling = NO;
//something to get the interface orientation updated
}
It's a small problem but it's driving me nuts. Any ideas?
Pure speculation but maybe try something like this?
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
}
Related
All view controllers in my app are working only in portrait orientation except one which can be portrait or landscape oriented.
I have some usage scenario like following:
I push controller which works in both orientations to UITabBarController
User change orientation from portait to landscape
User press "back button"
After these actions application remains in landscape orientation and does not change it automatically to portrait.
I control view controller orientation using supportedInterfaceOrientations (I use iOS 6.0). What I do wrong? How can I get correct behaviour when application automatically change orientation to allowed when user press back button? Thank you for answer!
In iOS 6 (and possibly earlier), if a view controller is offscreen when the device rotates, it does not get any notification. Nor does it get sent willAnimateRotationToInterfaceOrientation:duration: when it becomes the top view controller.
You need to keep track of the current orientation of the view controller and check the device orientation in viewWillAppear:. If they are different, you can use willAnimateRotationToInterfaceOrientation:duration: to set it correctly.
Since this is something you are likely to do a lot, you may want to create a generic superclass that your view controllers inherit from.
A typical solutions is:
#implementation MyHandlesOffscreenRotationController
{
BOOL isShowingPortrait;
}
- (void) viewDidLoad
{
[super viewDidLoad];
isShowingPortrait = UIInterfaceOrientationIsPortrait(
[[UIApplication sharedApplication] statusBarOrientation]);
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
BOOL currIsPortrait = UIInterfaceOrientationIsPortrait(
[[UIApplication sharedApplication] statusBarOrientation]);
if ((isShowingPortrait && !currIsPortrait) ||
(!isShowingPortrait && currIsPortrait)) {
[self willAnimateRotationToInterfaceOrientation:
[[UIApplication sharedApplication] statusBarOrientation]
duration:0.0f];
}
}
#end
Just override -(BOOL)shouldAutoRotate and - (NSUInteger)supportedInterfaceOrientations inside a UINavigationController category, then ViewController will force rotate to its supported orientation after pop from other ViewController.
#implementation UINavigationController (Rotate)
- (BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
#end
iOS 9 and above
At the time of pop just write the below-mentioned code in your viewWillAppear method.
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger: UIInterfaceOrientationPortrait]forKey:#"orientation"];
With this, your view will appear in portrait mode.
P.L. has a nice solution in this topic: Present and instantly dismiss an empty modal view controller which allows only portrait | landscape
How to change the device orientation programmatically in iOS 6
I want to rotate ONLY one of my views within my app to either landscape left or landscape right. All my other views are in portrait mode and I have set my app to support only portrait mode. With orientation being changed in iOS 6, I am not sure how to do this. I have tried the following posted below. Can anyone tell me what I am doing wrong? Thanks!
-(BOOL)shouldAutorotate {
return YES;
}
-(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight;
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft;
}
I have also tried:
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
return YES;//UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft;
}
-(void)didRotate:(NSNotification *)notification {
UIDeviceOrientation orientation = [[notification object] orientation];
if (orientation == UIDeviceOrientationLandscapeLeft) {
[theImage setTransform:CGAffineTransformMakeRotation(M_PI / -2.0)];
[self.view setTransform:CGAffineTransformMakeRotation(M_PI / 2.0)];
} else if (orientation == UIDeviceOrientationLandscapeRight) {
[theImage setTransform:CGAffineTransformMakeRotation(M_PI / -2.0)];
[self.view setTransform:CGAffineTransformMakeRotation(M_PI / -2.0)];
} else if (orientation == UIDeviceOrientationPortraitUpsideDown) {
[theImage setTransform:CGAffineTransformMakeRotation(M_PI / -2.0)];
[self.view setTransform:CGAffineTransformMakeRotation(M_PI / -2.0)];
} else if (orientation == UIDeviceOrientationPortrait) {
[theImage setTransform:CGAffineTransformMakeRotation(M_PI / 2.0)];
[self.view setTransform:CGAffineTransformMakeRotation(M_PI / 2.0)];
}
}
This worked for me How to force a UIViewController to Portrait orientation in iOS 6
Create a new category from UINavigationController overriding the rotating methods:
-(BOOL)shouldAutorotate
{
return [self.topViewController shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [self.topViewController preferredInterfaceOrientationForPresentation];
}
#end
There are changes in iOS 6 regarding handling view rotations. Only orientations defined in apps Info.plist are supported. Even if you are returning other ones.
Try to select all orientations as supported in your project.
Handling View Rotations
In iOS 6, your app supports the interface orientations defined in your app’s Info.plist file. A view controller can override the supportedInterfaceOrientations method to limit the list of supported orientations. Generally, the system calls this method only on the root view controller of the window or a view controller presented to fill the entire screen; child view controllers use the portion of the window provided for them by their parent view controller and no longer participate in directly in decisions about what rotations are supported. The intersection of the app’s orientation mask and the view controller’s orientation mask is used to determine which orientations a view controller can be rotated into.
You can override the preferredInterfaceOrientationForPresentation for a view controller that is intended to be presented full screen in a specific orientation.
In iOS 5 and earlier, 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. Generally, if your view controller is intended to be used as a child view controller, it should support all interface orientations.
When a rotation occurs for a visible view controller, the willRotateToInterfaceOrientation:duration:, willAnimateRotationToInterfaceOrientation:duration:, and didRotateFromInterfaceOrientation: methods are called during the rotation. The viewWillLayoutSubviews method is also called after the view is resized and positioned by its parent. If a view controller is not visible when an orientation change occurs, then the rotation methods are never called. However, the viewWillLayoutSubviews method is called when the view becomes visible. Your implementation of this method can call the statusBarOrientation method to determine the device orientation.
(C) Apple Docs: UIViewController
Follow the below steps
Create subclass of UINavigationController overriding the rotating methods.
In AppDelegate, create a BOOL islandscape property.
When a view is pushed/poped/present/dismiss, adjust this BOOL value.
Sample Project
I created a sample project for this which is working perfectly. Download and integrate in your project: https://www.dropbox.com/s/nl1wicbx52veq41/RotationDmeo.zip?dl=0
I have a:
TabbarController -> NavigationController -> ViewController -> ViewController
I Subclassed UITabBarController and add....
-(NSUInteger)supportedInterfaceOrientations{
if (self.selectedIndex >= 0 && self.selectedIndex < 100) {
for (id vC in [[self.viewControllers objectAtIndex:(unsigned long)self.selectedIndex] viewControllers]) {
if ([vC isKindOfClass:[CLASS_WHICH_SHOULD_ALLOW class]]) {
return UIInterfaceOrientationMaskPortrait + UIInterfaceOrientationMaskLandscape;
}
}
}
return UIInterfaceOrientationMaskPortrait;
}
I have been searching for the solution for hours!
So after implementing the needed methods everywhere. shouldAutorotate doesn't need to be set to YES because it is already set as default:
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
When it is time to show the UIViewController which needs the orientation different than the other views, I created a UIStoryboardSegue with this implementation inside:
#import "Showing.h"
#implementation Showing
- (void)perform{
NSLog(#"Showing");
UIViewController *sourceVC = self.sourceViewController;
UIViewController *presentingVC = self.destinationViewController;
[sourceVC.navigationController presentViewController:presentingVC
animated:YES
completion:nil];
}
#end
Inside the UIStoryboard I connected the views with this segue (showing):
It is just important, you are using
presentViewController:animated:completion:
AND NOT
pushViewController:animated:
otherwise the orientation won't be determined again.
I had been trying things like
[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
OR this one inside the UIViewController where the orientation should change, and I also tryied to call it inside my custom UIStoryboardSegues before presentingViewController and dismissViewController:
[UIViewController attemptRotationToDeviceOrientation];
OR
NSNumber *numPortrait = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:numPortrait forKey:#"orientation"];
But no one of them worked. Of course the last example shouldn't be an option, because if apple will change anything of their api this could cause problems inside your app.
I also tried to use the AppDelegate method and always determine the orientation inside this method after looking for the correct UIInterfaceOrientation of the actual visibleViewController but then it sometimes happened to crash when switching from one to another orientation. So I'm still wondering why its made so complicated and there seems also not to be any documentation where it is explained correctly.
Even following this part didn't help me.
UIViewController+OrientationPermissions.h
#interface UIViewController (OrientationPermissions)
+ (void)setSupportedOrientations:(UIInterfaceOrientationMask)supportedOrientations;
+ (UIInterfaceOrientationMask)supportedOrientations;
#end
UIViewController+OrientationPermissions.m
#implementation UIViewController (OrientationPermissions)
static UIInterfaceOrientationMask _supportedOrientations;
+ (void)setSupportedOrientations: (UIInterfaceOrientationMask)supportedOrientations {
_supportedOrientations = supportedOrientations;
}
+ (UIInterfaceOrientationMask)supportedOrientations {
return _supportedOrientations;
}
#end
In your UIApplication delegate
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return [UIViewController supportedOrientations];
}
Then on a desired view controller do something like
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[UIViewController setSupportedOrientations:UIInterfaceOrientationMaskAll];
}
Don't forget to reset mask before leaving this view controller
Note, if you are using UINavigationController or UITabBarController, see https://stackoverflow.com/a/28220616/821994 how to bypass that
Defiantly work Please try.
I solve after 2 days
//AppDelegate.m - this method is not available pre-iOS6 unfortunately
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
NSUInteger orientations = UIInterfaceOrientationMaskAllButUpsideDown;
if(self.window.rootViewController){
UIViewController *presentedViewController = [[(UINavigationController *)self.window.rootViewController viewControllers] lastObject];
orientations = [presentedViewController supportedInterfaceOrientations];
}
return orientations;
}
//MyViewController.m - return whatever orientations you want to support for each UIViewController
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
I'm in a real mess right now . I used apples sample code which does this :
Create a portrait view controller and a landscape view controller
Potrait event controller then registers for device orientation changed notifications
When device is rotated it presents a modal view controller for the landscape view or dismisses the landscape view if it is rotated back to portrait .
Everythings working as it should except for a little problem ....
Now to my problem . I used this to launch a rotatable view controller from a table view . It can be rotated and works fine . But if I initially launch it in landscape mode it will still launch as portrait . If i want landscape i have to afterwards AGAIN rotate it to landscape .I tried very hard to fix for this but failed . You can download and run the sample code from Apple Developer Site Here . Can anyone fix this code so that if launched in landscape mode it presents the modal view for the landscape view ? Otherwise I'll have to rewrite everything to use a single view controller .
These are the relevant portions of apples code :
- (void)viewDidLoad
{
self.view.backgroundColor = [UIColor colorWithRed:197.0/255.0 green:204.0/255.0 blue:211.0/255.0 alpha:1.0];
LandscapeViewController *viewController = [[LandscapeViewController alloc]
initWithNibName:#"LandscapeView" bundle:nil];
self.landscapeViewController = viewController;
[viewController release];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)orientationChanged:(NSNotification *)notification
{
// We must add a delay here, otherwise we'll swap in the new view
// too quickly and we'll get an animation glitch
[self performSelector:#selector(updateLandscapeView) withObject:nil afterDelay:0];
}
- (void)updateLandscapeView
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
{
[self presentModalViewController:self.landscapeViewController animated:YES];
isShowingLandscapeView = YES;
}
else if (deviceOrientation == UIDeviceOrientationPortrait && isShowingLandscapeView)
{
[self dismissModalViewControllerAnimated:YES];
isShowingLandscapeView = NO;
}
}
// override to allow orientations other than the default portrait orientation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{return (interfaceOrientation == UIInterfaceOrientationPortrait); // support only portrait}
I know this is probably no longer relevant to you, but I just came across the same glitch and here's my solution.
From the way you have your code set out (including how Apple sets it out)
- (void)updateLandscapeView
is only called once a Notification is sent out telling the ViewController of an orientation change: the issue here is, that this is the method responsible for checking the orientation it self. (i.e. Starting the application this method is not called and therefore it doesn't check whether the device is in any other orientation)
The solution is quite simple: Force cal themethod at launch, i.e. in viewDidLoad . . .
[self updateLandscapeView]
This will force call the method and check the interfaces orientation, after the first time, the method will be called again when ever it receives a notification for a changed Orientation
Hope this helps someone Out There
It seems that the device assumes portrait unless you specify landscape only in settings. Your only option would be in your portrait view in the loadview method to detect the orientation and to swap views during launch.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if(interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown ) {
//Load vertical interface
}
else
{
//load landscape
}
}
I am using a tab bar controller for my app. If I'm in one of the view controllers called "results" and the ios device rotates to landscape mode, it switches for another view I created called landScape. This has been done within the method
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
self.view=landScape;
} else {
self.view=originalView;
}
}
It works just fine whenever I am in that specific controller within the tab bar (example results view controller). However; If I go to another element in my tab bar controller and my phone is tilted in landscape mode and then I decide to go to resultsviewcontroller, it does not call upon my view landScape, instead it tries to autosize the view on its own and it looks awful. Should I call the method -(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration within my viewDidLoad method to solve the problem? Or is this completely wrong?
Thank you in advance!
The problem is you're only setting resultsViewController.view when it is the active view controller and detects a rotation. Try this:
- (void)setViewForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
self.view = UIInterfaceOrientationIsPortrait(orientation)
? originalView
: landScape;
}
- (void)viewWillAppear
{
[self setViewForInterfaceOrientation:[[UIDevice currentDevice] orientation];
[super viewWillAppear];
}
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self setViewForInterfaceOrientation:toInterfaceOrientation];
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
I have a UIView class which I add to my main UIViewController and I need to check the orientation of the device (iPad) at the launch of the app, in the viewDidLoad method. However because the class is a UIView (not UIViewController) I can't use methods such as willAnimateRotationToInterfaceOrientation.
So I attempted to use this in my UIView class:
if (([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft) ||
([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight)) {
However, testing with some breakpoints, whatever the orientation is, the of statement is never called, its skips right passed it. So what do you suggest I do to overcome this issue?
I need to somehow detect the orientation from the UIView class.
Thanks.
Where are you placing the check? The location could easily explain why it's not being called. To get rotation info, you could register for a notification, or have your view controller call a method in your view. Sample code for the latter:
// ViewController.m
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self.customView willRotateToOrientation:toInterfaceOrientation];
}
// CustomView.m
- (void)willRotateToOrientation:(UIInterfaceOrientation)newOrientation {
// Handle rotation
}
The view controller method is one you override; the view's method should be declared in a header.
Update:
Alternatively, you can find out the rotation in the controller's `viewWillAppear':
// ViewController.m
- (void)viewWillAppear {
[self.customView willRotateToOrientation:[[UIDevice currentDevice] orientation];
}
The method in your view will be called accordingly.
One thing you can to is to register for orientation notification from NSNotificationCenter:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
...
- (void)orientationChanged:(NSNotification *)notification
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
// do things
}
This is however suboptimal since iPad may be laying flat on the table when app starts, and you'll get UIDeviceOrientationUnknown then. Been here, done that...
I ended up doing a trivial check like this:
BOOL landscape = self.bounds.size.width > self.bounds.size.height;
if (landscape)
// landscape stuff
else
// portrait stuff
But in my case the view changed aspect ratio upon rotation. If this is your case too, it should work fine.