iPad橫豎屏適配

一、監聽橫豎屏的切換

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;
}

其中enablePortraitappdelegate.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、通過方法shouldAutorotatesupportedInterfaceOrientations實現個別界面豎屏:

-(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的官方文檔中已經說明,
(_cmdObjective-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;
}

結果:

可看到橫屏下右側有空白,是因為橫屏是的tableViewframe還是豎屏時的frame,因此應該在屏幕旋轉時重新設置tableViewframe

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    self.tableView.frame = [UIScreen mainScreen].bounds;
}

此時橫豎屏是cell都能撐滿整個屏幕
注意:如果在cell中設置btnframe是在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方法,繼承自UIViewControllerUITableViewControllerUICollectionController的控制器布局最好在viewWillLayoutSubviews里面,這樣橫豎屏切換不用再設置frame
2、 willRotateToInterfaceOrientation:duration:在ios8之后已經廢棄,建議使用viewWillTransitionToSize: withTransitionCoordinator:

1、在viewWillTransitionToSize: withTransitionCoordinator:方法中獲取屏幕寬高:[UIScreen mainScreen].bounds.size.width,這個方法是在屏幕旋轉之前執行的,按理說此時屏幕寬高是旋轉之前的屏幕寬高,但是某些情況下會出問題,問題截圖:


問題的四個狀態:
1、勾選Upside DownLaunch Screen File選中LaunchScreen
2、勾選Upside DownLaunch Screen File清空
3、不勾選Upside DownLaunch Screen File選中LaunchScreen
4、不勾選Upside DownLaunch 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)對屏幕進行適配
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.監聽屏幕旋轉方向 在處理iOS橫豎屏時,經常會和UIDeviceOrientation、UIInterface...
    彬至睢陽閱讀 2,548評論 1 6
  • 關于橫豎屏適配,有一句說一句,坑挺深的。之前做Vision和畢設的時候就處理過橫豎屏問題,不過當時的功力太淺,明顯...
    HarwordLiu閱讀 37,378評論 26 137
  • iOS 中橫豎屏切換的功能,在開發iOS app中總能遇到。以前看過幾次,感覺簡單,但是沒有敲過代碼實現,最近又碰...
    零度_不結冰閱讀 2,217評論 0 0
  • 前言 現在大部分的智能移動設備通過自動旋轉,能夠自動切換去呈現最適合當前屏幕顯示的內容,無疑大大提升了使用者的用戶...
    BladeWayne閱讀 10,043評論 2 54
  • 原文地址在我的個人主頁 你可能非常了解用不同的方式去適配不同尺寸的iPhone屏幕,在適配iPhone屏幕時你需要...
    jmstack閱讀 7,397評論 1 22