在開發(fā)中囊咏,有時候我們需要讓某些特定的頁面為橫屏展示毕贼,而從這個頁面離開或者進(jìn)入其他頁面時為豎屏,起初我僅僅依賴了UIViewController的屏幕旋轉(zhuǎn)方法簡單的處理况木,但是當(dāng)需求慢慢在變化時畅蹂,我發(fā)覺這并不能滿足我的需求:
比如導(dǎo)航頁需要橫屏展示健无,但是從導(dǎo)航頁上面某個按鈕跳轉(zhuǎn)的頁面需要豎屏展示,而再從這個頁面返回到導(dǎo)航頁時液斜,如果屏幕為豎屏且用戶關(guān)閉了屏幕旋轉(zhuǎn)鎖定累贤,則導(dǎo)航頁應(yīng)繼續(xù)展示橫屏。
下面筆記寫的比較亂少漆,可直接查看Demo in Github
屏幕旋轉(zhuǎn)的方案
我現(xiàn)在使用的解決方案是CMMotionManager
臼膏,啟動設(shè)備的運(yùn)動更新,通過給定的隊列向給定的處理程序提供數(shù)據(jù)示损,對屏幕旋轉(zhuǎn)的監(jiān)測渗磅。
CMMotionManager可以理解為是CoreMotion Framework的中央管理器,也可以理解為運(yùn)動服務(wù)屎媳。這些服務(wù)提供了獲取加速機(jī)數(shù)據(jù)夺溢、旋轉(zhuǎn)數(shù)據(jù)和磁力數(shù)據(jù)等。
首先我在info.plist中把設(shè)備的方向只勾選了豎屏烛谊。
AppDelegate 中需要實現(xiàn)的方法
- (UIInterfaceOrientationMask)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window {
// iPhone doesn't support upside down by default, while the iPad does. Override to allow all orientations always, and let the root view controller decide what's allowed (the supported orientations mask gets intersected).
UIViewController * presentdeVC = [self.class topViewControllerWithPresentedViewController];
if ([presentdeVC isKindOfClass:NSClassFromString(@"NaviController")])
return UIInterfaceOrientationMaskAllButUpsideDown;
return UIInterfaceOrientationMaskPortrait;
}
監(jiān)聽設(shè)備運(yùn)動
/// 開啟屏幕旋轉(zhuǎn)的檢測
- (void)startListeningDirectionOfDevice {
if (self.motionManager == nil) {
self.motionManager = [[CMMotionManager alloc] init];
}
// 提供設(shè)備運(yùn)動數(shù)據(jù)到指定的時間間隔 刷新數(shù)據(jù)的評率
self.motionManager.deviceMotionUpdateInterval = 0.3;
// 判斷設(shè)備傳感器是否可用
if (self.motionManager.deviceMotionAvailable) {
// 啟動設(shè)備的運(yùn)動更新,通過給定的隊列向給定的處理程序提供數(shù)據(jù)嘉汰。
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
[self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
}];
} else {
[self setMotionManager:nil];
}
}
停止監(jiān)聽設(shè)備運(yùn)動
- (void)stopListeningDirectionOfDevice {
if (_motionManager) {
[_motionManager stopDeviceMotionUpdates];
_motionManager = nil;
}
}
根據(jù)設(shè)備運(yùn)動丹禀,更新設(shè)備屏幕方向
- (void)handleDeviceMotion:(CMDeviceMotion *)deviceMotion {
if ([UIDevice bb_canRotate] == false) {
return;
}
double x = deviceMotion.gravity.x;
double y = deviceMotion.gravity.y;
if (fabs(y) >= fabs(x)) {// 豎屏
if (y < 0) {
if (self.previousInterfaceOrientation == UIInterfaceOrientationPortrait) {
return;
}
[self applyInterfaceOrientation:UIInterfaceOrientationPortrait];
}
else {
if (self.previousInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
return;
}
[self applyInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown];
}
}
else { // 橫屏
if (x < 0) {
if (self.previousInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
return;
}
[self applyInterfaceOrientation:UIInterfaceOrientationLandscapeRight];
}
else {
if (self.previousInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
return;
}
[self applyInterfaceOrientation:UIInterfaceOrientationLandscapeLeft];
}
}
}
強(qiáng)制修改屏幕方向
- (void)applyInterfaceOrientation:(UIInterfaceOrientation)orientation {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
UIDevice *currentDevice = [UIDevice currentDevice];
UIDeviceOrientation currentDeviceOrientation = currentDevice.orientation;
/// mark:強(qiáng)制旋轉(zhuǎn)有時無效的解決方案: 強(qiáng)制前先設(shè)置為UIDeviceOrientationUnknown
[currentDevice setValue:@(UIDeviceOrientationUnknown) forKey:@"orientation"];
SEL selector = NSSelectorFromString(@"setOrientation:");
if (![currentDevice respondsToSelector:selector]) {
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:currentDevice];
// 從2開始是因為0 1 兩個參數(shù)已經(jīng)被selector和target占用
[invocation setArgument:&orientation atIndex:2];
[invocation invoke];
[currentDevice endGeneratingDeviceOrientationNotifications];
[UIDevice setForceOrientation:currentDeviceOrientation];
}
獲取屏幕鎖定狀態(tài)
現(xiàn)在存在的問題是,屏幕旋轉(zhuǎn)開關(guān)被鎖定時,橫屏狀態(tài)下不應(yīng)該可以旋轉(zhuǎn)頁面双泪,而Apple提供的api中持搜,并沒有獲取控制中心中屏幕旋轉(zhuǎn)鎖定的開關(guān)
通過監(jiān)聽UIDeviceOrientationDidChangeNotification
通知測試:
- 當(dāng)app在前臺時,如果屏幕旋轉(zhuǎn)開關(guān)鎖定了焙矛,怎么旋轉(zhuǎn)設(shè)備都不會觸發(fā)此通知葫盼,只有在app啟動、從后臺進(jìn)入前臺或者被激活時村斟,才會觸發(fā)此通知
- 當(dāng)屏幕旋轉(zhuǎn)開關(guān)被關(guān)閉時贫导,設(shè)備旋轉(zhuǎn)時都會觸發(fā)此通知的
解決方法:
- 注冊
UIDeviceOrientationDidChangeNotification
通知
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientaionDidChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
}
- 當(dāng)觸發(fā)監(jiān)聽的方法時,根據(jù)設(shè)備的方向確定屏幕是否可以旋轉(zhuǎn)
/// 當(dāng)用戶鎖定了屏幕旋轉(zhuǎn)開關(guān)時蟆盹,且app在前臺時孩灯,設(shè)備旋轉(zhuǎn)不會觸發(fā)此通知,當(dāng)app啟動逾滥、從后臺進(jìn)入前臺或者被激活時峰档,都會觸發(fā)此通知
+ (void)deviceOrientaionDidChange:(NSNotification *)noty {
self.forceOrientation = [UIDevice currentDevice].orientation;
[InterfaceOrientationUtil sharedInsatnce].previousInterfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
UIDevice *device = [UIDevice currentDevice] ;
/**
* 取得當(dāng)前Device的方向,Device的方向類型為Integer
*
* 必須調(diào)用beginGeneratingDeviceOrientationNotifications方法后寨昙,此orientation屬性才有效讥巡,否則一直是0。orientation用于判斷設(shè)備的朝向舔哪,與應(yīng)用UI方向無關(guān)
*
* @param device.orientation
*
*/
NSLog(@"%@", noty.userInfo);
switch (device.orientation) {
case UIDeviceOrientationLandscapeLeft:
case UIDeviceOrientationLandscapeRight:
[self setBb_canRotate:YES];//只有當(dāng)用戶把手機(jī)旋轉(zhuǎn)到橫屏的時候來去觸發(fā)判斷是否支持橫屏
break;
default:
[self setBb_canRotate:NO];
break;
}
}
在監(jiān)聽設(shè)備的運(yùn)動更新的方法中欢顷,根據(jù)canRotate判斷是否可以旋轉(zhuǎn)屏幕