📜 ⬆️ ⬇️

Understanding autorotation in iOS 6

Hello friends!

If you are creating applications for gadgets from Apple, then you probably know that the iOS update to version 6 recently occurred.
Along with other new features, Apple has made changes to the autorotation mechanism.
Just in case, let me remind you that autorotation is a mechanism that allows you to use the device both in portrait (stretched in height) and landscape (stretched in width) orientation, and also change this orientation when you turn the device.
image
If content is displayed in both orientations in your application (and especially if you need to prohibit rotation on some screens), I bet that you already have some questions .
If you do not use the function of changing the orientation of the screen - the difference could not be noticed. However, knowing how autorotation works in iOS6 will in any case be useful and useful in the future.

How was it before iOS 6


IOS devices support 4 possible screen orientations, described by their respective system constants:

In iOS 5 and earlier versions, the autorotation mechanism uses the method

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { //  YES    return (interfaceOrientation == UIInterfaceOrientationPortrait); } 

When a device changes its orientation in space, by calling this method, the system requests the active controller (view controller) whether it supports the transition to this orientation.
When the method is called, the interfaceOrientation parameter contains one of 4 possible values, and the method should return YES if you need to rotate the application window (or NO otherwise).
Thus, for each individual controller, it is sufficient to override the shouldAutorotateToInterfaceOrientation: method and specify the types of orientation it supports.
')
The UISupportedInterfaceOrientations key in Info.plist contains a list of orientations supported by the application (you can also select them in the Summary section of your Targets) and is used by the system only to determine the initial orientation when the application starts.



If no type of orientation is indicated - nothing terrible will happen, the application will be launched in the usual portrait (UIInterfaceOrientationPortrait).

Became in iOS 6


In iOS 6, the shouldAutorotateToInterfaceOrientation method is deprecated, while the other autorotation logic is supported by the supportedInterfaceOrientations and shouldAutorotate .

When changing the position of the device (or when the controller is presented modally), the system polls the top-most full-screen controller (top-most full-screen view controller). At the same time, the first call is called the mustAutorotate , and then (only in the case of returning the value YES) the call is supportedInterfaceOrientations to get a bit mask describing the supported positions. For example, the following code can be used to support ordinary portrait and both landscape orientations.

 - (NSInteger)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight; } 

Next, the system uses the value obtained from the supportedInterfaceOrientations , performing a conjunction (bitwise "AND") with a list of orientations globally supported by the application (taken from Info.plist or as a result of the AppDelegate application: supportedInterfaceOrientationsForWindow method:). According to the results of the operation, a turn (or not) occurs.

In short, the decision is made by operation
 app_mask & topmost_controller_mask 

where app_mask is taken from Info.plist (or application: supportedInterfaceOrientationsForWindow :) , and topmost_controller_mask as a result of the supportedInterfaceOrientations call of the top full-screen controller.

Also consider the following points:



These are the changes. This move is dictated by the desire of Apple to transfer responsibility for making decisions about the supported position of the screen from each specific active controller to the controller-containers and the application itself.

Apple’s key thoughts (Session 236 from WWDC 2012) are as follows:


What to do with it


When developing a new project that requires support for iOS 5 and earlier, Apple recommends trying to emulate iOS 6 mechanisms:

However, what to do if it is necessary to migrate (preferably with minimal effort) on iOS 6 an already existing project, in which decisions about turns are made by different end controllers? Using the supportedInterfaceOrientations / shouldAutorotate methods next to shouldAutorotateToInterfaceOrientation will not save the situation if these controllers are not root and not top-most full-screen. To force the controller-containers to listen to the opinion of controlled ones, you can use the following approaches.

1. Category.
Using the category, redefine new methods so that the corresponding top controllers are polled for rotation. For example, for a UINavigationController this might look like this:

 @implementation UINavigationController (RotationIOS6) -(BOOL)shouldAutorotate { return [self.topViewController shouldAutorotate]; } -(NSUInteger)supportedInterfaceOrientations { return [self.topViewController supportedInterfaceOrientations]; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return [self.topViewController preferredInterfaceOrientationForPresentation]; } @end 


2. Inheritance.
Implement the same as in clause 1, but by inheriting from UINavigationController - when there is no need to globally expose all UINavigationControllers at once.

 // CustomNavigationController.h @interface CustomNavigationController : UINavigationController @end 


 // CustomNavigationController.m #import "CustomNavigationController.h" @implementation CustomNavigationController -(BOOL)shouldAutorotate { return [self.topViewController shouldAutorotate]; } -(NSUInteger)supportedInterfaceOrientations { return [self.topViewController supportedInterfaceOrientations]; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return [self.topViewController preferredInterfaceOrientationForPresentation]; } @end 


3. Method swizzling.
For runtime lovers and hardcore fans - using swizzling , override new methods so as to actually use the calls to the old usual method shouldAutorotateToInterfaceOrientation:.

(code taken from here )

 @implementation AppDelegate void SwapMethodImplementations(Class cls, SEL left_sel, SEL right_sel) { Method leftMethod = class_getInstanceMethod(cls, left_sel); Method rightMethod = class_getInstanceMethod(cls, right_sel); method_exchangeImplementations(leftMethod, rightMethod); } + (void)initialize { if (self == [AppDelegate class]) { #ifdef __IPHONE_6_0 SwapMethodImplementations([UIViewController class], @selector(supportedInterfaceOrientations), @selector(sp_supportedInterfaceOrientations)); SwapMethodImplementations([UIViewController class], @selector(shouldAutorotate), @selector(sp_shouldAutorotate)); #endif } } @end @implementation UIViewController (iOS6Autorotation) #ifdef __IPHONE_6_0 /* * We've swizzled the new iOS 6 autorotation callbacks onto their iOS 5 and iOS 4 equivalents * to preserve existing functionality. * */ - (BOOL)sp_shouldAutorotate { BOOL shouldAutorotate = YES; if ([self respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) { NSUInteger mask = 0; if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) { mask |= UIInterfaceOrientationMaskPortrait; } if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeLeft]) { mask |= UIInterfaceOrientationMaskLandscapeLeft; } if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeRight]) { mask |= UIInterfaceOrientationMaskLandscapeRight; } if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown]) { mask |= UIInterfaceOrientationMaskPortraitUpsideDown; } if (mask == 0) { // Shouldn't autorotate to *any* orientation. shouldAutorotate = NO; } } else { // This actually calls the original method implementation // instead of recursively calling into this method implementation. shouldAutorotate = [self sp_shouldAutorotate]; } return shouldAutorotate; } - (NSUInteger)sp_supportedInterfaceOrientations { NSUInteger mask = 0; /* * In iOS 6, Apple dramatically changed the way autorotation works. * Rather than having each view controller respond to shouldAutorotateToInterfaceOrientation: * to specify whether or not it could support a particular orientation, the responsibility was * shifted to top-level container view controllers. That means UINavigationController becomes * responsible for declaring whether or not an orientation is supported. Since our app * has logic for how to autorotate on a per view controller basis, we call through to the * swizzled version of supportedInterfaceOrientations for the topViewController. * */ if ([self isKindOfClass:[UINavigationController class]]) { return [[(UINavigationController *)self topViewController] supportedInterfaceOrientations]; } if ([self respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) { if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) { mask |= UIInterfaceOrientationMaskPortrait; } if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeLeft]) { mask |= UIInterfaceOrientationMaskLandscapeLeft; } if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeRight]) { mask |= UIInterfaceOrientationMaskLandscapeRight; } if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown]) { mask |= UIInterfaceOrientationMaskPortraitUpsideDown; } } else { // This actually calls the original method implementation // instead of recursively calling into this method implementation. mask = [self sp_supportedInterfaceOrientations]; } return mask; } #endif @end 

In this case, no changes to the existing code will be required at all - the runtime magic will do its job. However, no matter how tempting it looks, this code is not strongly recommended for use (find out why) .

In my case, it was most convenient to use categories.

I hope the material presented is useful to someone and will help save the most valuable developer resource - time :)

Useful links:

Source: https://habr.com/ru/post/155969/


All Articles