概述
寫(xiě)代碼就是在不斷填坑的過(guò)程中慢慢成長(zhǎng),程序員哪有不遇坑的呢缔莲?
這篇文章來(lái)談?wù)刬OS中橫豎屏切換的一些坑掀宋,橫豎屏切換在App中很常見(jiàn)雳刺,本來(lái)我也以為做這個(gè)功能是很簡(jiǎn)單的一件事戚丸,但半年前我在做公司項(xiàng)目的過(guò)程中就遇到了不少麻煩划址,使用了一種比較tricky的方法,在屏幕方向切換時(shí)程序偶爾會(huì)崩掉限府,雖然后來(lái)經(jīng)過(guò)修改解決了夺颤,但導(dǎo)致控制屏幕方向的代碼散落在不同角落,不易閱讀胁勺,維護(hù)起來(lái)更不方便拂共。前陣子在寫(xiě)一個(gè)播放器JFPlayer時(shí),采用了另一種比較好的方法姻几,便在此總結(jié)一下各種坑吧。
這里分幾種情況:
- 所有界面都支持橫豎屏切換
- 只有一個(gè)(或幾個(gè))界面固定方向势告,其他界面支持橫豎屏切換
- 只有一個(gè)(或幾個(gè))界面支持橫豎屏切換蛇捌,其他界面固定方向
一般情形
所有界面都支持橫豎屏切換
這是最簡(jiǎn)單的,只需要在【General】-->【Deployment Info】-->【Device Orientation】勾選上相應(yīng)地方向就行了咱台。
這樣設(shè)備是豎屏?xí)r所有界面都是豎屏的络拌,設(shè)備是橫屏?xí)r所以界面都是橫屏的。
注意:
這里有一個(gè)坑回溺,在 iOS 9 以后春贸,橫屏?xí)r狀態(tài)欄會(huì)隱藏混萝,如果想要顯示狀態(tài)欄,需要手動(dòng)控制萍恕。
在
Info.plist
中 設(shè)置View controller-based status bar appearance
值為YES
逸嘀,在 view controller 中重寫(xiě)prefersStatusBarHidden
返回false
override var prefersStatusBarHidden: Bool {
return false
}
## 特殊情形
### 只有一個(gè)(或幾個(gè))界面固定方向,其他界面支持橫豎屏切換
其實(shí)這種情況一般比較少見(jiàn)允粤,在【General】-->【Deployment Info】-->【Device Orientation】勾選希望支持的方向崭倘,然后在需要固定方向的視圖控制器中實(shí)現(xiàn)如下兩個(gè)方法即可。
```swift
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
只有一個(gè)(或幾個(gè))界面支持橫豎屏切換类垫,其他界面固定方向
這是最常見(jiàn)的情況司光,大多數(shù)App都會(huì)是這種需求,比如視頻直播類App悉患,只有一個(gè)界面需要支持橫豎屏残家,其他界面都是豎屏。
這種情況有兩種方法來(lái)實(shí)現(xiàn)售躁。
方法一
設(shè)置設(shè)備僅支持豎屏坞淮,監(jiān)聽(tīng)屏幕旋轉(zhuǎn)的通知,在收到通知后手動(dòng)旋轉(zhuǎn)視圖迂求。
不推薦D胙巍!
在【General】-->【Deployment Info】-->【Device Orientation】勾選方向:
取消屏幕自動(dòng)旋轉(zhuǎn):
override var shouldAutorotate: Bool {
return false
}
在 viewDidLoad
中監(jiān)聽(tīng)通知:
NotificationCenter.default.addObserver(self, selector: #selector(didChangeOrientation), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
控制視圖旋轉(zhuǎn):
func didChangeOrientation() {
if (UIDevice.current.orientation == .portrait) {
UIView.animate(withDuration: 0.2, animations: {
self.view.transform = .identity
self.view.bounds = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
})
} else {
UIView.animate(withDuration: 0.2, animations: {
self.view.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2));
self.view.bounds = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width)
})
}
}
填坑一
這種方法需要精確地控制該界面的所有View(包括子View)的旋轉(zhuǎn)以及旋轉(zhuǎn)的方向揩局,設(shè)備豎屏?xí)r和橫屏?xí)r旋轉(zhuǎn)的角度不一樣毫玖,Home鍵在左和在右旋轉(zhuǎn)的角度也不一樣,在界面復(fù)雜時(shí)凌盯,特別麻煩付枫,所以不推薦。
填坑二
因?yàn)橹辉O(shè)置了豎屏驰怎,所以當(dāng)橫屏?xí)r阐滩,如果有鍵盤(pán)彈出,鍵盤(pán)是豎屏?xí)r的樣式县忌。
解決辦法:在【General】-->【Deployment Info】-->【Device Orientation】中加上橫屏?xí)r的方向掂榔。
注意:
在僅支持豎屏模式下,不能直接重載
shouldAutorotate
并返回true
症杏。程序會(huì)崩掉装获,并拋出這個(gè)錯(cuò)誤Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and [Demo.ViewController shouldAutorotate] is returning YES'
原因是,你如果設(shè)備僅支持豎屏厉颤,不能在view controller中控制界面自動(dòng)旋轉(zhuǎn)穴豫。
方法二
設(shè)置設(shè)備支持橫豎屏,讓其自動(dòng)旋轉(zhuǎn)逼友,實(shí)現(xiàn)一個(gè)基類控制器只支持一個(gè)方向精肃,其他固定方向的界面繼承基類秤涩,同時(shí)監(jiān)聽(tīng)屏幕旋轉(zhuǎn)通知,處理一些特殊需求司抱。
強(qiáng)烈推薦?鹁臁!
在【General】-->【Deployment Info】-->【Device Orientation】勾選方向:
在基類控制器中重寫(xiě)兩個(gè)控制橫豎屏的方法:
class BaseViewController: UIViewController {
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
}
填坑一
如果VieController 是放在UINavigationController或者UITabBarController中状植,需要重寫(xiě)它們的方向控制方法浊竟。
class RootNavigationController: UINavigationController {
override var shouldAutorotate: Bool {
guard let topViewController = topViewController else {
return true
}
return topViewController.shouldAutorotate
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
guard let topViewController = topViewController else {
return .all
}
return topViewController.supportedInterfaceOrientations
}
override var preferredStatusBarStyle: UIStatusBarStyle {
guard let topViewController = topViewController else {
return .default
}
return topViewController.preferredStatusBarStyle
}
}
填坑二
播放器中都會(huì)有這樣一個(gè)功能,點(diǎn)擊按鈕將界面變成全屏津畸,該怎樣做呢振定?
答案是:將設(shè)備強(qiáng)制橫屏,改變狀態(tài)欄方向
func fullScreenButtonPressed(_ button: UIButton?) {
// force change device and status bar orientation, that toggle the UIApplicationDidChangeStatusBarOrientation notification
if isFullScreen {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
updateStatusBarAppearanceHidden(false)
UIApplication.shared.statusBarOrientation = .portrait
} else {
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
updateStatusBarAppearanceHidden(false)
UIApplication.shared.statusBarOrientation = .landscapeRight
}
}
其實(shí)在 stackoverflow
上你能搜到另外一個(gè)答案
- (void)interfaceOrientation:(UIInterfaceOrientation)orientation
{
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 = orientation;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
// 橫屏
- (IBAction)landscapAction:(id)sender {
[self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
}
// 豎屏
- (IBAction)portraitAction:(id)sender {
[self interfaceOrientation:UIInterfaceOrientationPortrait];
}
但我不知道如何將這段代碼改為Swift肉拓,也不知道這段代碼在 iOS 10 下是否仍然奏效后频。
填坑三
在大多數(shù)視頻類App中,播放視頻的窗口一開(kāi)始是在界面上方的一小塊區(qū)域暖途,點(diǎn)擊全屏按鈕或設(shè)備橫屏?xí)r才全屏的卑惜,該如何實(shí)現(xiàn)這個(gè)功能呢?
可能你們會(huì)想驻售,我們可以監(jiān)聽(tīng)設(shè)備旋轉(zhuǎn)的通知露久,在設(shè)備旋轉(zhuǎn)時(shí),判斷現(xiàn)在是橫屏還是豎屏欺栗,然后改變view的約束條件(constraint)來(lái)改變大小毫痕。
這里有一個(gè)小技巧,我們只需要設(shè)置好view的長(zhǎng)寬比與屏幕的長(zhǎng)寬比一致就可以了迟几,一切交給 auto layout
消请,不用我們?nèi)ゲ傩摹?/p>
// push入這個(gè)控制器的上一個(gè)控制器必須只支持豎屏,不然在手機(jī)橫著時(shí)类腮,push入這個(gè)控制器時(shí)視頻的尺寸有問(wèn)題臊泰。
player.snp.makeConstraints { (make) in
make.top.equalTo(view.snp.top)
make.left.equalTo(view.snp.left)
make.right.equalTo(view.snp.right)
make.height.equalTo(view.snp.width).multipliedBy(UIScreen.main.bounds.width/UIScreen.main.bounds.height)
// 寬高比也可以為 16:9
}
注意:
使用上述技巧時(shí),push入這個(gè)控制器的上一個(gè)控制器必須只支持豎屏蚜枢,不然在手機(jī)橫著時(shí)缸逃,push入這個(gè)控制器時(shí)視頻的尺寸有問(wèn)題。
其他方法
本來(lái)還有另外一種方法的厂抽,但只在 iOS 9下有效需频,半年前我用的就是這個(gè)方法。我在寫(xiě)這篇文章時(shí)重新試了下修肠,發(fā)現(xiàn)在iOS 10下已經(jīng)行不通了,也在這里記錄下吧户盯。
在 AppDelegate
中聲明一個(gè)屬性 allowRotation
來(lái)標(biāo)記是否允許旋轉(zhuǎn)嵌施,實(shí)現(xiàn)如下方法控制橫豎屏:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if allowRotation {
return .landscape
}
return .portrait
}
然后在控制器的viewWillAppear
方法中饲化,把 allowRotation
設(shè)為 true
,這樣界面就會(huì)橫屏顯示了吗伤。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.allowRotation = true
}
這里也有一個(gè)坑吃靠,在view退出前,要把 allowRotation
設(shè)為 false
, 不然退出后足淆,之前豎屏顯示的界面會(huì)變成橫屏巢块。
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.allowRotation = false
}
其實(shí)在 iOS 9下還有其他坑的,需要在這個(gè)控制器的界面顯示或消失是精確地修改 allowRotation
的值巧号,比如在該界面橫屏?xí)r彈出豎屏的登錄窗口族奢、在該界面橫屏?xí)r退回來(lái)豎屏的界面等,不然隨時(shí)會(huì)閃退丹鸿,總之就是會(huì)有一些亂七八糟意料之外的錯(cuò)誤出現(xiàn)越走,但現(xiàn)在我的系統(tǒng)已經(jīng)升級(jí)為 iOS 10 了,已經(jīng)無(wú)法重現(xiàn)了靠欢。
這不是一個(gè)好方法廊敌,也不推薦使用!门怪!
總結(jié)
- 控制橫豎屏切換還是使用系統(tǒng)默認(rèn)的方式最好骡澈,不用去操心子view是否旋轉(zhuǎn),旋轉(zhuǎn)的方向等掷空,也可以避免一些奇奇怪怪的問(wèn)題發(fā)生肋殴,少踩一些坑!拣帽!故推薦方法二疼电。
- iOS 每升級(jí)一個(gè)版本都會(huì)有一些方法不能用,導(dǎo)致頻頻閃退减拭,巨坑1尾颉!
- 使用的方法不對(duì)必然會(huì)踩一些莫名其妙的坑拧粪,在此謹(jǐn)記修陡。