一、監聽橫豎屏的切換
1、通知方式:
//監聽UIApplicationDidChangeStatusBarFrameNotification通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(changeRotate:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
- (void)changeRotate:(NSNotification*)noti {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
NSLog(@"%d -- ", deviceOrientation);
}
如果使用這個通知,當iPhone/iPad旋轉的時候,你會得到的旋轉方向會是所有的UIDeviceOrientationUnknown(屏幕方向向下)
和UIDeviceOrientationFaceUp(屏幕方向向上)
等,即如下枚舉中所有值
typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
UIDeviceOrientationUnknown,
UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom
UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top
UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right
UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left
UIDeviceOrientationFaceUp, // Device oriented flat, face up
UIDeviceOrientationFaceDown // Device oriented flat, face down
} __TVOS_PROHIBITED;
如果僅僅是監聽橫豎屏的話,可以監聽UIApplicationDidChangeStatusBarOrientationNotification
通知,即:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
此時會監聽下面幾種情況的通知:
UIDeviceOrientationPortrait
UIDeviceOrientationPortraitUpsideDown
UIDeviceOrientationLandscapeLeft
UIDeviceOrientationLandscapeRight
也可以用以下通知:
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doRotateAction:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)doRotateAction:(NSNotification *)notification {
if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait || [[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown) {
NSLog(@"豎屏");
} else if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft || [[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight) {
NSLog(@"橫屏");
}
}
2、通過方法willRotateToInterfaceOrientation: duration
來判斷
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {// 橫屏
NSLog(@"");
} else {//豎屏
}
}
其中可以根據UIInterfaceOrientation
值來判斷橫豎屏,UIInterfaceOrientation
為:
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown,
UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait,
UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight,
UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft
} __TVOS_PROHIBITED;
注意:在手機開啟豎排方向鎖定時,上述方法無效
為了解決即使在手機開啟豎排方向鎖定時,仍然能知道手機是豎向還是橫向的問題,可以使用加速計
加速計的原理:
- 檢測設備在x,y,z軸上的加速度(加速度范圍為-1 ~ 1)
- 根據加速度的數值,判斷手機屏幕方向。
實現代碼:
#import "ViewController.h"
#import <CoreMotion/CoreMotion.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 加速計
if (motionManager.accelerometerAvailable) {
motionManager.accelerometerUpdateInterval = 1;
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
if (error) {
[motionManager stopAccelerometerUpdates];
NSLog(@"error");
} else {
double x = accelerometerData.acceleration.x;
double y = accelerometerData.acceleration.y;
if (fabs(y) >= fabs(x)) {
if (y >= 0){
NSLog(@"upSideDown");
} else{
NSLog(@"Portrait");
}
} else{
if (x >= 0){
NSLog(@"right");
}else{
NSLog(@"left");
}
}
}
}];
} else {
NSLog(@"This device has no accelerometer");
}
}
二、強制個別界面豎屏顯示
1、通過appdelegate
的代理方法application:supportedInterfaceOrientationsForWindow
實現:
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window{
if(_enablePortrait){
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskLandscape | UIInterfaceOrientationMaskPortrait;
}
其中enablePortrait
為appdelegate.h
中的一個BOOL
值屬性,如果某個界面僅僅支持豎屏,就設置為YES
,否則不用管,如下在某個控制器中的代碼:
//不會直接變為橫屏
-(void)viewWillAppear:(BOOL)animated{
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
delegate.enablePortrait = YES;
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
AppDelegate *delegate = (AppDelegate *)[UIApplicationsharedApplication].delegate;
delegate.enablePortrait = NO;
}
這樣設置完之后控制器就不支持橫屏顯示
2、通過方法shouldAutorotate
和supportedInterfaceOrientations
實現個別界面豎屏:
-(BOOL)shouldAutorotate{
return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
shouldAutorotate
意思是是否支持自動旋轉 supportedInterfaceOrientations
意思是旋轉的方向,當添加了以上兩個方法你會發現并沒什么用,看看官方文檔:
過描述我們可以發現這兩個方法需要設置在根視圖中,意思是說確實這個頁面是否旋轉旋轉的方向都是要根據根視圖的這兩個方法來判斷,如果有導航控制器,在
ViewController
中的shouldAutorotate
不會被調用,會調用到導航控制器中的shouldAutorotate
方法,解決問題:新建
BaseNavigationController
繼承UINavigationController
,實現方法
//是否自動旋轉
-(BOOL)shouldAutorotate{
return self.topViewController.shouldAutorotate;
}
//支持的方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return self.topViewController.supportedInterfaceOrientations;
}
//一開始的方向 很重要
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return self.topViewController.preferredInterfaceOrientationForPresentation;
}
使ViewController
的導航控制器為BaseNavigationController
類型,并實現方法:
-(BOOL)shouldAutorotate{
return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
//- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
// return UIInterfaceOrientationMaskLandscape;
//}
//一開始就左橫屏,必須supportedInterfaceOrientations方法返回的集合包括UIInterfaceOrientationLandscapeLeft
//- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
// return UIInterfaceOrientationLandscapeLeft;
//}
此時你會發現屏幕僅僅支持豎屏,橫屏不再起作用,如果這兩個方法未實現,則默認支持橫豎屏
如果大部分界面是豎屏,個別界面是橫屏,最好寫個繼承自UIViewController
類的基類BaseViewController
,在基類中實現方法:
-(BOOL)shouldAutorotate{
return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
之后再創建控制器就繼承BaseViewController
,則創建的控制器就默認是豎屏,需要橫屏界面重新實現supportedInterfaceOrientations
方法即可
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
三、強制界面僅僅支持橫屏或豎屏
1、通過KVC
,在ViewDidLoad
中實現以下代碼:
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft] forKey:@"orientation"];
[[self class] attemptRotationToDeviceOrientation];
這里不是直接使用蘋果的私有變量,而是利用KVC的方法 間接的調用此方法,可以上架,不會被打回
2、利用 NSInvocation 調用 對象的消息
//使用這里的代碼也是oK的。 這里利用 NSInvocation 調用 對象的消息
//使用這里的代碼也是oK的。 這里利用 NSInvocation 調用 對象的消息
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
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 = UIInterfaceOrientationLandscapeLeft;//橫屏
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
第一個參數需要接收一個指針,也就是傳遞值的時候需要傳遞地址
第二個參數:需要給指定方法的第幾個參數傳值
注意:設置參數的索引時不能從0開始,因為0已經被self(target)
占用,1已經被_cmd(selector)
占用在NSInvocation
的官方文檔中已經說明,
(_cmd
在Objective-C
的方法中表示當前方法的selector
,正如同self
表示當前方法調用的對象實例。)
[invocationsetArgument:&valatIndex:2];
調用NSInvocation對象的invoke方法*只要調用invocation的invoke方法,就代表需要執行NSInvocation對象中制定對象的指定方法,并且傳遞指定的參數
[invocation invoke];
四、獲取當前屏幕的方向
UIInterfaceOrientation currentOrient = [UIApplication sharedApplication].statusBarOrientation;
五、ipad橫屏時tableView左右有空白
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc]initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *indentifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:indentifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:indentifier];
}
cell.textLabel.text = [NSString stringWithFormat:@"cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:indentifier];"];
return cell;
}
結果:
如上使用的是系統的
cell
,會出現以下左右空白問題,這是系統cell
自動設置的內邊距,我們要自定義cell
替換系統cell
,cell
中添加一個button
做測試:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.testBtn = [[UIButton alloc]init];
self.testBtn.backgroundColor = [UIColor orangeColor];
[self addSubview:self.testBtn];
}
return self;
}
- (void)layoutSubviews{
[super layoutSubviews];
self.testBtn.frame = CGRectMake(0, 10, [UIScreen mainScreen].bounds.size.width, 30);
}
此時只需要將控制器中的系統cell
替換為自定義的cell
即可
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *indentifier = @"cell";
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:indentifier];
if (!cell) {
cell = [[TestCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:indentifier];
}
return cell;
}
結果:
可看到橫屏下右側有空白,是因為橫屏是的tableView
的frame
還是豎屏時的frame
,因此應該在屏幕旋轉時重新設置tableView
的frame
:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
self.tableView.frame = [UIScreen mainScreen].bounds;
}
此時橫豎屏是cell都能撐滿整個屏幕
注意:如果在cell
中設置btn
的frame
是在init
方法中,橫屏時cell
右側仍然有空白,因此規范寫法就是在layoutSubviews
設置frame
,如:
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.testBtn = [[UIButton alloc]initWithFrame:CGRectMake(0, 10, [UIScreen mainScreen].bounds.size.width, 30)];
self.testBtn.backgroundColor = [UIColor orangeColor];
[self addSubview:self.testBtn];
}
return self;
}
細心觀察會發現tableViewd的分割線有內縮進,為了讓tableView分割線自定義且為了適配ios7、8、9以上系統,應在cell將要出現時:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
并且在viewDidLoad中增加代碼:
if ([self.tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) {
self.tableView.cellLayoutMarginsFollowReadableWidth = NO;
}
if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsMake(0, 16, 0, 16)];
}
此時即可實現分割線內邊距,不過還可以隱藏系統分割線,自己在自定義cell
中設置
最后代碼如下:
//cell中代碼
@implementation TestCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.testBtn = [[UIButton alloc]init];
self.testBtn.backgroundColor = [UIColor orangeColor];
[self addSubview:self.testBtn];
}
return self;
}
- (void)layoutSubviews{
[super layoutSubviews];
self.testBtn.frame = CGRectMake(0, 10, [UIScreen mainScreen].bounds.size.width, 30);
}
@end
//控制器中代碼
#import "ViewController.h"
#import "TestCell.h"
@interface ViewController ()<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong)UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc]initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
if ([self.tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) {
self.tableView.cellLayoutMarginsFollowReadableWidth = NO;
}
if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsMake(0, 16, 0, 16)];
}
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
self.tableView.frame = [UIScreen mainScreen].bounds;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 100;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *indentifier = @"cell";
TestCell *cell = [tableView dequeueReusableCellWithIdentifier:indentifier];
if (!cell) {
cell = [[TestCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:indentifier];
}
return cell;
}
@end
六、iphone適配豎屏、iPad適配橫豎屏設置
可進行如下設置:
我注意到有些項目中可以直接分別設置iphone和iPad的橫豎屏、狀態欄樣式,但是我新建的項目中并沒有,網上也沒有找到資料,知道的讀者麻煩說下,如圖:
最后
1、繼承自UIView
的類設置子控件frame
最好在layoutSubViews
方法,繼承自UIViewController
、UITableViewController
、UICollectionController
的控制器布局最好在viewWillLayoutSubviews
里面,這樣橫豎屏切換不用再設置frame
2、 willRotateToInterfaceOrientation:duration:
在ios8之后已經廢棄,建議使用viewWillTransitionToSize: withTransitionCoordinator:
坑
1、在viewWillTransitionToSize: withTransitionCoordinator:
方法中獲取屏幕寬高:[UIScreen mainScreen].bounds.size.width
,這個方法是在屏幕旋轉之前執行的,按理說此時屏幕寬高是旋轉之前的屏幕寬高,但是某些情況下會出問題,問題截圖:
問題的四個狀態:
1、勾選
Upside Down
,Launch Screen File
選中LaunchScreen
2、勾選
Upside Down
,Launch Screen File
清空3、不勾選
Upside Down
,Launch Screen File
選中LaunchScreen
4、不勾選
Upside Down
,Launch Screen File
清空然后我們在控制器中打印屏幕寬度
#define kSrceenW [UIScreen mainScreen].bounds.size.width
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
NSLog(@"%f %f", [UIScreen mainScreen].bounds.size.width, kSrceenW);
}```
根據打印結果我們會發現情況2、3、4三種情況打印的是旋轉之前的屏幕寬度,情況1打印的是旋轉之后屏幕寬度,坑,所以我們最好用方法參數`size`(控制器view的size)對屏幕進行適配