iOS Rotation

iOS屏幕旋轉(zhuǎn)學(xué)習(xí)筆記
iOS開(kāi)發(fā)中使用屏幕旋轉(zhuǎn)功能的相關(guān)方法

1、基本知識(shí)點(diǎn)解讀

了解屏幕旋轉(zhuǎn)首先需要區(qū)分兩種 orientation
1.1、device orientation
設(shè)備的物理方向
1.2、interface orientation
界面顯示的方向

iOS提供了在設(shè)備旋轉(zhuǎn)時(shí),界面顯示發(fā)生相應(yīng)適配的能力,以達(dá)到方便用戶(hù)使用并提供最佳顯示效果的目的。
開(kāi)發(fā)者需要指定應(yīng)用支持的顯示方向,并對(duì)界面顯示做出對(duì)應(yīng)的適配。
由于界面適配的工作量相當(dāng)大,目前國(guó)內(nèi)的應(yīng)用大都只支持默認(rèn)的豎屏方向。

2、屏幕旋轉(zhuǎn)的流程

加速計(jì)是整個(gè)IOS屏幕旋轉(zhuǎn)的基礎(chǔ)。
依賴(lài)加速計(jì),設(shè)備才可以判斷出當(dāng)前的設(shè)備方向。
當(dāng)加速計(jì)檢測(cè)到方向變化的時(shí)候,會(huì)發(fā)出
UIDeviceOrientationDidChangeNotification 通知。
屏幕旋轉(zhuǎn)的流程如下:
1>、加速計(jì)來(lái)識(shí)別設(shè)備的旋轉(zhuǎn)方向。
發(fā)送 UIDeviceOrientationDidChangeNotification 設(shè)備旋轉(zhuǎn)的通知。
2>、app 接收到旋轉(zhuǎn)事件(通知事件)。
2>、app 通過(guò)AppDelegate通知當(dāng)前程序的KeyWindow。
3>、Window 會(huì)知會(huì)它的 rootViewController,判斷該view controller所支持的旋轉(zhuǎn)方向,完成旋轉(zhuǎn)。
4>、如果存在 modal 的view controller的話(huà),系統(tǒng)則會(huì)根據(jù) modal 的view controller,來(lái)判斷是否要進(jìn)行旋轉(zhuǎn)。

UIDevice 對(duì)象可以選擇性的(是否)接收
UIDeviceOrientationDidChangeNotification 通知。

// 是否已經(jīng)開(kāi)啟了設(shè)備方向改變的通知
@property(nonatomic,readonly,getter=isGeneratingDeviceOrientationNotifications)
BOOL generatesDeviceOrientationNotifications
__TVOS_PROHIBITED;

// 開(kāi)啟接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)beginGeneratingDeviceOrientationNotifications
__TVOS_PROHIBITED;     // nestable

// 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
- (void)endGeneratingDeviceOrientationNotifications__TVOS_PROHIBITED;


在 app 代理里面結(jié)束接收 設(shè)備旋轉(zhuǎn)的通知事件, 后續(xù)的屏幕旋轉(zhuǎn)都會(huì)失效

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 結(jié)束接收接收 UIDeviceOrientationDidChangeNotification 通知
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
    return YES;
}

3、如何決定interface orientation (界面方向)

3.1、全局控制
Info.plist文件中,有一個(gè)Supported interface orientations,可以配置整個(gè)應(yīng)用的屏幕方向,此處為 全局控制
3.2、UIWindow
iOS6的UIApplicationDelegate提供了下述方法,能夠指定 UIWindow 中的界面的屏幕方向:

// 該方法默認(rèn)值為 Info.plist 中配置的 Supported interface orientations 項(xiàng)的值。
- (NSUInteger)application:(UIApplication *)application  supportedInterfaceOrientationsForWindow:(UIWindow *)window  NS_AVAILABLE_IOS(6_0); 

iOS中通常只有一個(gè) window,所以此處的控制也可以視為全局控制。
3.3、controller (單個(gè)界面控制)
只有以下兩種情況:
當(dāng)前 controller 是 window 的 rootViewController
當(dāng)前 controller 是 modal 模式的時(shí)(controller 為 modal 顯示的控制器),orientations相關(guān)方法才會(huì)起作用(才會(huì)被調(diào)用),當(dāng)前controller及其所有的childViewController都在此作用范圍內(nèi)。

3.4、最終支持的屏幕方向
一個(gè)界面最后支持的屏幕方向,是取 (全局控制 ∩ UIWindow 中的界面控制 ∩ 單個(gè)界面控制)
的交集。
如果全局控制支持所有屏幕方向,UIWindow 中的界面控制支持橫屏,當(dāng)個(gè)界面中只是支持橫屏向右,那么最后界面只會(huì)以橫屏向右顯示,并且不支持旋轉(zhuǎn)到其他的方向。

如果最終的交集為空,在iOS6以后會(huì)拋出
UIApplicationInvalidInterfaceOrientationException
崩潰異常。

4、API 使用講解

4.1**UIViewController Rotation 的 api **


// To make it more convenient for applications to adopt rotation, a view controller may implement the below methods. Your UIWindow's frame should use [UIScreen mainScreen].bounds as its frame.
// 為了讓控制器更加方便的旋轉(zhuǎn),視圖控制器可以實(shí)現(xiàn)下面這些方法。你的 UIWindow 的 frame 將要使用 UIScreen mainScreen].bounds 作為他的 frame。

@interface UIViewController (UIViewControllerRotation)

// call this method when your return value from shouldAutorotateToInterfaceOrientation: changes
// if the current interface orientation does not match the current device orientation, a rotation may occur provided all relevant view controllers now return YES from shouldAutorotateToInterfaceOrientation:

//  試著去旋轉(zhuǎn)到界面的方向
// 使用場(chǎng)景是 interface orientation和device orientation 不一致,
// 希望通過(guò)重新指定 interface orientation 的值,立即實(shí)現(xiàn)二者一致;
// 如果這時(shí)只是更改了支持的 interface orientation 的值,沒(méi)有調(diào)用attemptRotationToDeviceOrientation,那么下次 device orientation 變化的時(shí)候才會(huì)實(shí)現(xiàn)二者一致,關(guān)鍵點(diǎn)在于能不能立即實(shí)現(xiàn)。
+ (void)attemptRotationToDeviceOrientation NS_AVAILABLE_IOS(5_0) __TVOS_PROHIBITED;

// Applications should use supportedInterfaceOrientations and/or shouldAutorotate..  
// 應(yīng)用將要使用界面支持的方向,或者將要自動(dòng)旋轉(zhuǎn) (在 iOS6 以后被禁用, 要兼容 iOS 6 還是需要實(shí)現(xiàn)這個(gè)方法)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS(2_0, 6_0) __TVOS_PROHIBITED;

// New Autorotation support.  
// 新的自動(dòng)旋轉(zhuǎn)的支持 (上面的方法被下面兩個(gè)方法替代)

// 將要自動(dòng)旋轉(zhuǎn) (在 iOS6 開(kāi)始啟用)返回 yes 為支持旋轉(zhuǎn),no 為不支持旋轉(zhuǎn)
- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;

// 支持的界面方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;

// Returns interface orientation masks.
// 返回現(xiàn)在正在顯示的用戶(hù)界面方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;

// The rotating header and footer views will slide out during the rotation and back in once it has completed.
- (nullable UIView *)rotatingHeaderView NS_DEPRECATED_IOS(2_0,8_0, "Header views are animated along with the rest of the view hierarchy") __TVOS_PROHIBITED;     // Must be in the view hierarchy. Default returns nil.
- (nullable UIView *)rotatingFooterView NS_DEPRECATED_IOS(2_0,8_0, "Footer views are animated along with the rest of the view hierarchy") __TVOS_PROHIBITED;     // Must be in the view hierarchy. Default returns nil.

// 獲取用戶(hù)界面的方向 (方法在 iOS8 被禁用,方法是只讀的)
@property(nonatomic,readonly) UIInterfaceOrientation interfaceOrientation NS_DEPRECATED_IOS(2_0,8_0) __TVOS_PROHIBITED;

// Notifies when rotation begins, reaches halfway point and ends.
// 當(dāng)旋轉(zhuǎn)開(kāi)始,到達(dá)一半,結(jié)束的時(shí)候,將通過(guò)下列方法得到通知。

// 將要旋轉(zhuǎn)到用戶(hù)界面
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(2_0,8_0, "Implement viewWillTransitionToSize:withTransitionCoordinator: instead") __TVOS_PROHIBITED;

// 已經(jīng)從某個(gè)用戶(hù)界面開(kāi)始旋轉(zhuǎn)
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation NS_DEPRECATED_IOS(2_0,8_0) __TVOS_PROHIBITED;

// 將要?jiǎng)赢?huà)旋轉(zhuǎn)到用戶(hù)界面
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(3_0,8_0, "Implement viewWillTransitionToSize:withTransitionCoordinator: instead") __TVOS_PROHIBITED;

// 界面切換到一半的控制
- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(2_0, 5_0) __TVOS_PROHIBITED;
- (void)didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS(2_0, 5_0) __TVOS_PROHIBITED; // The rotating header and footer views are offscreen.
- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(2_0, 5_0) __TVOS_PROHIBITED; // A this point, our view orientation is set to the new orientation.

@end

4.2 api 的說(shuō)明

// Applications should use supportedInterfaceOrientations and/or shouldAutorotate.. 
// 應(yīng)用將要使用界面支持的方向,或者將要自動(dòng)旋轉(zhuǎn) (在 iOS6 以后被禁用, 要兼容 iOS 6 還是需要實(shí)現(xiàn)這個(gè)方法)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS(2_0, 6_0) __TVOS_PROHIBITED;

這一個(gè)接口被下面兩個(gè)接口替代 (在 iOS6 以后)

// 將要自動(dòng)旋轉(zhuǎn) (在 iOS6 開(kāi)始啟用)返回 yes 為支持旋轉(zhuǎn),no 為不支持旋轉(zhuǎn)
- (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;

// 支持的界面方向 (在 iOS6 開(kāi)始啟用)
- (UIInterfaceOrientationMask)supportedInterfaceOrientations NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;

如果需要兼容 iOS6 之前的系統(tǒng)還是需要實(shí)現(xiàn)老的接口,要不 app 只能支持豎屏不能旋轉(zhuǎn)

4.3 api 接口的簡(jiǎn)單使用 (iOS 6 之后)

當(dāng)只需要鎖定屏幕的旋轉(zhuǎn)不需要設(shè)置屏幕的旋轉(zhuǎn)方向 ( 方向固定是豎直)

// 是否支持屏幕旋轉(zhuǎn)
-(BOOL)shouldAutorotate {
    return NO;
}

開(kāi)啟屏幕旋轉(zhuǎn)并設(shè)置屏幕旋轉(zhuǎn)支持的方向

// 是否支持屏幕旋轉(zhuǎn) (返回 NO 后面?zhèn)z方法不調(diào)用,后面只支持豎直方向)
-(BOOL)shouldAutorotate {
    return NO;
}
// 支持屏幕旋轉(zhuǎn)的方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    
  //  通過(guò)設(shè)置返回的枚舉值來(lái)改變屏幕旋轉(zhuǎn)支持的方向 
  //(iPad上的默認(rèn)返回值是UIInterfaceOrientationMaskAll,
  //  iPhone上的默認(rèn)返回值是UIInterfaceOrientationMaskAllButUpsideDown)
    return UIInterfaceOrientationMaskAll;
}

// Returns interface orientation masks. (返回最優(yōu)先顯示的屏幕方向)
// 同時(shí)支持Portrait和Landscape方向,但想優(yōu)先顯示Landscape方向,那軟件啟動(dòng)的時(shí)候就會(huì)先顯示Landscape,在手機(jī)切換旋轉(zhuǎn)方向的時(shí)候仍然可以在Portrait和Landscape之間切換;
// 返回現(xiàn)在正在顯示的用戶(hù)界面方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {

};

** 屏幕旋轉(zhuǎn)設(shè)置注意點(diǎn):**
在iOS 4 and 5,都是由具體的view controller來(lái)決定對(duì)應(yīng)的view的orientation設(shè)置。
而在iOS 6,則是由 top-most controller來(lái)決定 view 的 orientation 設(shè)置。

舉個(gè)例子:
你的app的rootViewController是navigation controller "nav", 在”nav"里的stack依次是:main view -> sub view > sub sub view,而main view里有一個(gè)button會(huì)present modal view "modal view".

那么for ios 4 and 5,在ipad里,如果你要上述view都僅支持橫屏orientation,你需要在上面的main view, sub view, sub sub view, model view里都添加:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {  
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation==UIInterfaceOrientationLandscapeRight);  
}  

而對(duì)于iOS6, 由于是由 top-most controller 來(lái)設(shè)置 orientation,因此你在main view, sub view, sub sub view里添加下面的代碼是沒(méi)有任何效果的,
而應(yīng)該是在nav controller里添加下列代碼。
而modal view則不是在nav container里,因此你也需要在 modal view controller 里也添加下列代碼。

- (BOOL)shouldAutorotate  {  
    return YES;  
}  

-(NSUInteger)supportedInterfaceOrientations {  
    return UIInterfaceOrientationMaskLandscape;  
}  

** 注意:**
你需要自定義一個(gè)UINavigationController的子類(lèi)for "nav controller",這樣才可以添加上述代碼。和navigation controller類(lèi)似,tab controller里的各個(gè)view的orientation設(shè)置應(yīng)該放在tab controller里

** api 說(shuō)明:**
** attemptRotationToDeviceOrientation 使用說(shuō)明:**

// call this method when your return value from shouldAutorotateToInterfaceOrientation: changes 
// if the current interface orientation does not match the current device orientation, a rotation may occur provided all relevant view controllers now return YES from shouldAutorotateToInterfaceOrientation: 
// 該方法的使用場(chǎng)景是 interface orientation和device orientation 不一致,但希望通過(guò)重新指定 interface orientation 的值,立即實(shí)現(xiàn)二者一致;
// 如果這時(shí)只是更改了支持的 interface orientation 的值,沒(méi)有調(diào)用attemptRotationToDeviceOrientation,那么下次 device orientation 變化的時(shí)候才會(huì)實(shí)現(xiàn)二者一致,關(guān)鍵點(diǎn)在于能不能立即實(shí)現(xiàn)。
+ (void)attemptRotationToDeviceOrientation {
      // code
}

舉個(gè)例子:
假設(shè)當(dāng)前的 interface orientation 只支持 Portrait。
如果 device orientation 變成 Landscape,那么 interface orientation 仍然顯示 Portrait;
如果這時(shí)我們希望 interface orientation 也變成和 device orientation 一致的 Landscape,
以iOS 6 為例,需要先將 supportedInterfaceOrientations 的返回值改成Landscape,然后調(diào)用 attemptRotationToDeviceOrientation方法,系統(tǒng)會(huì)重新詢(xún)問(wèn)支持的 interface orientation,已達(dá)到立即更改當(dāng)前 interface orientation 的目的。

** supportedInterfaceOrientations 使用說(shuō)明: **
此方法返回當(dāng)前viewController 支持的方向. 但是, 只有兩種情況下此方法才會(huì)生效:
1、當(dāng)前viewController是window的rootViewController.
2、當(dāng)前viewController是modal模式的. 即, 此viewController是被調(diào)用
presentModalViewController 而顯示出來(lái)的.

在以上兩種情況中,UIViewController.supportedInterfaceOrientations 方法會(huì)作用于前viewController和所有childViewController. 以上兩種情況之外, UIKit并不會(huì)理會(huì)你的supportedInterfaceOrientations 方法.

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}

如果某個(gè)viewController實(shí)現(xiàn)了以上方法. 則, 此viewController就支持豎方向和左旋轉(zhuǎn)方向. 此viewController的所有childViewController也同時(shí)支持這兩個(gè)方向, 不多不少.

** preferredInterfaceOrientationForPresentation 使用說(shuō)明: **
此方法也僅有在當(dāng)前viewController是rootViewController或者是modal模式時(shí)才生效.

** shouldAutorotate 使用說(shuō)明:**
用于設(shè)置當(dāng)前viewController是否支持自動(dòng)旋轉(zhuǎn). 如果,你需要viewController
暫停自動(dòng)旋轉(zhuǎn)一小會(huì)兒. 那么可以通過(guò)這個(gè)方法來(lái)實(shí)現(xiàn).
同樣的, 此方法也僅有在當(dāng)前viewController是rootViewController或者是modal模式時(shí)才生效.

5、實(shí)現(xiàn)屏幕的旋轉(zhuǎn)

5.1、在 iOS6 之后存在豎屏幕固定和需要進(jìn)行旋轉(zhuǎn)的界面的需求配置:

for ios6的top-most controller決定orientation設(shè)置,導(dǎo)致這樣一個(gè)問(wèn)題:
在 top-most controller里的views無(wú)法擁有不相同的orientation設(shè)置。
例如:for iphone, 在nav controller里,你有main view, sub view and sub sub view,前2個(gè)都只能打豎,而sub sub view是用來(lái)播放video,可以打橫打豎。
那么在ios 4 and 5里可以通過(guò)在main view and sub view的shouldAutorotateToInterfaceOrientation里設(shè)置只能打豎,而在sub sub view的shouldAutorotateToInterfaceOrientation設(shè)置打豎打橫即可。
而在ios 6里則無(wú)法實(shí)現(xiàn)這種效果,因?yàn)樵趍ain view, sub view and sub sub view的orientation設(shè)置是無(wú)效的,只能夠在nav controller里設(shè)置。那么你可能想著用下列代碼在nav controller里控制哪個(gè)view打豎,哪個(gè)view打橫:

-(NSUInteger)supportedInterfaceOrientations{  
    if([[self topViewController] isKindOfClass:[SubSubView class]])  
        return UIInterfaceOrientationMaskAllButUpsideDown;  
    else  
        return UIInterfaceOrientationMaskPortrait;  
}  

是的,這樣可以使得在main view and sub view里無(wú)法打橫,而sub sub view橫豎都行。但問(wèn)題來(lái)了,如果在sub sub view時(shí)打橫,然后back to sub view,那么sub view是打橫顯示的!

目前想到的解決方法只能是把sub sub view脫離nav controller,以modal view方式來(lái)顯示。
這樣就可以在modal view里設(shè)置打橫打豎,而在nav controller里設(shè)置只打豎。

說(shuō)了那么多,其實(shí)如果你的app的所有view的orientation的設(shè)置是統(tǒng)一的,那么你可以簡(jiǎn)單的在plist file里設(shè)置即可,不用添加上面的代碼。而如果你添加了上面的代碼,就會(huì)覆蓋plist里orientation的設(shè)置。

in iOS 6, 當(dāng)view controller present時(shí),
不會(huì)call willRotateToInterfaceOrientation:duration:,
willAnimateRotationToInterfaceOrientation:duration:,
and didRotateFromInterfaceOrientation: methods,只有在發(fā)生rotate的時(shí)候才會(huì)call。

5.2、強(qiáng)制旋轉(zhuǎn)

iOS兩個(gè)強(qiáng)制旋轉(zhuǎn)屏幕的方法
有時(shí)候, 需要不隨系統(tǒng)旋轉(zhuǎn), 而是強(qiáng)制旋轉(zhuǎn)到某一個(gè)角度. 最典型的場(chǎng)景就是視頻播放器, 當(dāng)點(diǎn)擊了全屏按鈕的時(shí)候, 需要橫過(guò)來(lái)顯示.
對(duì)于IOS5及以前的版本, 可以用下面的方法:

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
    SEL selector = NSSelectorFromString(@"setOrientation:");
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
    [invocation setSelector:selector];
    [invocation setTarget:[UIDevice currentDevice]];
    int val = UIInterfaceOrientationLandscapeRight;
    [invocation setArgument:&val atIndex:2];
    [invocation invoke];
}

對(duì)于IOS6及以后的版本. UIDevice.setOrientation從隱藏變?yōu)橐瞥?只能通過(guò)設(shè)置UIView.transform 的方法來(lái)實(shí)現(xiàn).

UIView.transform 最后一個(gè)方法是設(shè)置UIView 的transform屬性來(lái)強(qiáng)制旋轉(zhuǎn).
見(jiàn)下代碼:

//設(shè)置statusBar
[[UIApplication sharedApplication] setStatusBarOrientation:orientation];
//計(jì)算旋轉(zhuǎn)角度
float arch;
if (orientation == UIInterfaceOrientationLandscapeLeft)  { 
      rch = -M_PI_2;
 }  else if (orientation == UIInterfaceOrientationLandscapeRight) {
      arch = M_PI_2;
} else {
      arch = 0;
}
//對(duì)navigationController.view 進(jìn)行強(qiáng)制旋轉(zhuǎn)
self.navigationController.view.transform = CGAffineTransformMakeRotation(arch);
self.navigationController.view.bounds = UIInterfaceOrientationIsLandscape(orientation) ? CGRectMake(0, 0, SCREEN_HEIGHT, SCREEN_WIDTH) : initialBounds;

需要注意的是:
1、當(dāng)然我們可以對(duì)當(dāng)前viewController進(jìn)行旋轉(zhuǎn), 對(duì)任何view旋轉(zhuǎn)都可以.但是, 你會(huì)發(fā)現(xiàn)navigationBar還橫在那里. 所以, 我們最好對(duì)一個(gè)占滿(mǎn)全屏的view進(jìn)行旋轉(zhuǎn). 在這里我們旋轉(zhuǎn)的對(duì)象是self.navigationController.view, 當(dāng)然self.window也可以, help yourself~
2、我們需要顯式的設(shè)置bounds. UIKit并不知道你偷偷摸摸干了這些事情, 所以沒(méi)法幫你自動(dòng)設(shè)置.

IOS6及以后
對(duì)于IOS6及以后的版本, 如果想方便的單獨(dú)控制每個(gè)viewController的方向. 則可以使用這樣:
對(duì)于非modal模式的viewController:如果不是rootViewController,則重寫(xiě)supportedInterfaceOrientations,preferredInterfaceOrientationForPresentation以及shouldAutorotate方法, 按照當(dāng)前viewController的需要返回響應(yīng)的值.如果是rootViewController,則如下重寫(xiě)方法:

-(NSUInteger)supportedInterfaceOrientations{ 
        return self.topMostViewController.supportedInterfaceOrientations;
}
-(BOOL)shouldAutorotate{ 
        return [self.topMostViewController shouldAutorotate];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{ 
return [self.topMostViewController preferredInterfaceOrientationForPresentation];
}
-(UIViewController*)topMostViewController{ 
//找到當(dāng)前正在顯示的viewController并返回.
}

顯而易見(jiàn), 我們巧妙的繞開(kāi)了UIKit只調(diào)用rootViewController的方法的規(guī)則. 把決定權(quán)交給了當(dāng)前正在顯示的viewController.
對(duì)于modal
模式的viewController. 則按照需要重寫(xiě)supportedInterfaceOrientations,
preferredInterfaceOrientationForPresentation
以及shouldAutorotate 方法即可.

四、強(qiáng)制屏幕旋轉(zhuǎn)
如果interface和device方向不一樣,想強(qiáng)制將interface旋轉(zhuǎn)成device的方向,可以通過(guò)attemptRotationToDeviceOrientation實(shí)現(xiàn),但是如果想將interface強(qiáng)制旋轉(zhuǎn)成任一指定方向,該方式就無(wú)能為力了。
1、私有方法

[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait]; 

2、旋轉(zhuǎn)view的transform
也可以通過(guò)旋轉(zhuǎn)view的transform屬性達(dá)到強(qiáng)制旋轉(zhuǎn)屏幕方向的目的,但個(gè)人感覺(jué)這不是靠譜的思路,可能會(huì)帶來(lái)某些詭異的問(wèn)題。

3、主動(dòng)觸發(fā) orientation 機(jī)制
要是能主動(dòng)觸發(fā)系統(tǒng)的 orientation 機(jī)制,調(diào)用 orientation 相關(guān)方法,使新設(shè)置的 orientation 值起作用就好了。這樣只要提前設(shè)置好想要支持的 orientation,然后主動(dòng)觸發(fā) orientation 機(jī)制,便能實(shí)現(xiàn)將 interface orientation旋轉(zhuǎn)至任意方向的目的。

關(guān)于手動(dòng)旋轉(zhuǎn)的設(shè)置:

方式一、直接設(shè)置 UIDevice 的 orientation (可能被拒)

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {  
    [[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:(id)UIInterfaceOrientationPortrait];  
} 

方式二、沒(méi)有改變 UIDevice 的 orientation,而是改變某個(gè)view的 transform,利用 CGAffineTransformMakeRotation 來(lái)達(dá)到目的,比如:

self.view.transform = CGAffineTransformMakeRotation(M_PI/2) 

下面講解采用第二種方式的各版本手動(dòng)旋轉(zhuǎn):思想是首先設(shè)置 statusBarOrientation,然后再改變某個(gè)view的方向跟 statusBarOrientation 一致!
IOS6手動(dòng)旋轉(zhuǎn):

  1. 那既然是旋轉(zhuǎn),最少也得有2個(gè)方向,那么還是少不了上面說(shuō)的那個(gè)硬性條件,先在plist里面設(shè)置好所有可能需要旋轉(zhuǎn)的方向。既然是手動(dòng)旋轉(zhuǎn),那么就要關(guān)閉自動(dòng)旋轉(zhuǎn):
- (BOOL)shouldAutorotate{  
        return NO;  
}  

2.手動(dòng)觸發(fā)某個(gè)按鈕,調(diào)用方法,這個(gè)方法的實(shí)現(xiàn)如下:

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];  
self.view.transform = CGAffineTransformMakeRotation(M_PI/2);  
self.view.bounds = CGRectMake(0, 0, kScreenHeight, 320);  

注意:

  1. 只需要改變self.view.transform,那么self.view的所有subview都會(huì)跟著自動(dòng)變;其次因?yàn)榉较蜃兞耍詓elf.view的大小需要重新設(shè)置,不要使用self.view.frame,而是用bounds。
  2. 如果shouldAutorotate 返回YES的話(huà),下面設(shè)置setStatusBarOrientation 是不管用的!setStatusBarOrientation只有在shouldAutorotate 返回NO的情況下才管用!

IOS5、IOS4手動(dòng)旋轉(zhuǎn):

  • (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
    return (interfaceOrientation == [UIApplication sharedApplication].statusBarOrientation);
    }
    1.在需要手動(dòng)旋轉(zhuǎn)的viewController里的 shouldAutorotateToInterfaceOrientation 方法設(shè)置 interfaceOrientation == [UIApplicationsharedApplication].statusBarOrientation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{  
    return (interfaceOrientation == [UIApplication sharedApplication].statusBarOrientation);  
}  

2.手動(dòng)觸發(fā)某個(gè)按鈕,調(diào)用方法,這個(gè)方法的實(shí)現(xiàn)如下:

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];  
self.view.transform = CGAffineTransformMakeRotation(M_PI/2);  
self.view.bounds = CGRectMake(0, 0, kScreenHeight, 320);  

注意:只需要改變self.view.transform,那么self.view的所有subview都會(huì)跟著自動(dòng)變;其次因?yàn)榉较蜃兞?,所以self.view的大小需要重新設(shè)置,不要使用self.view.frame,而是用bounds。

經(jīng)驗(yàn)分享:
1.IOS6里面,如果一個(gè)項(xiàng)目里面需要各種旋轉(zhuǎn)支持,有自動(dòng),有手動(dòng),那么我們可以新建2個(gè)navController或者tabbarController的子類(lèi),一個(gè)是不旋轉(zhuǎn),一個(gè)旋轉(zhuǎn),那么所有需要旋轉(zhuǎn)的UINavigationController都可以用這個(gè)子類(lèi)來(lái)代替!包括我們可以定制短信呀、郵件呀的旋轉(zhuǎn)!2.supportedInterfaceOrientations 方法一般是寫(xiě)UIInterfaceOrientationMask方向,但是如果程序要兼容4.3以下的SDK(4.3以下的SDK必須是4.5以下的Xcode,不支持IOS6),那么在用4.5以下的Xcode編譯的時(shí)候通不過(guò)!所以可以用statusBarOrientation代替或者直接寫(xiě)死數(shù)字!

-(NSUInteger)supportedInterfaceOrientations{  
    return [UIApplication sharedApplication].statusBarOrientation;  
}  

3.一般都不建議在程序里面直接調(diào)用 UIDeviceOrientation 的方向,而是用 UIInterfaceOrientation,他們之間是不同的!

UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,  
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft  

UIApplication.h


// Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa).
// This is because rotating the device to the left requires rotating the content to the right.
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;

/* This exception is raised if supportedInterfaceOrientations returns 0, or if preferredInterfaceOrientationForPresentation
   returns an orientation that is not supported.
*/
UIKIT_EXTERN NSString *const UIApplicationInvalidInterfaceOrientationException NS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED;

typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} __TVOS_PROHIBITED;

以組合的方式更加方便的使用這些枚舉值

萬(wàn)能的stackoverflow上提供了一種主動(dòng)觸發(fā)的方式:
UIViewController *vc = [[UIViewController alloc]init]; 
[self presentModalViewController:vc animated:NO]; 
[self dismissModalViewControllerAnimated:NO]; 
[vc release]; 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,742評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,580評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,297評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,688評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,875評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,438評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,183評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,384評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,612評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,022評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,297評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,093評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,330評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容