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;

注意:在手机开启竖排方向锁定时,上述方法无效

为了解决即使在手机开启竖排方向锁定时,仍然能知道手机是竖向还是横向的问题,可以使用加速计

加速计的原理:


iPad横竖屏适配_第1张图片

iPad横竖屏适配_第2张图片
  • 检测设备在x,y,z轴上的加速度(加速度范围为-1 ~ 1)
  • 根据加速度的数值,判断手机屏幕方向。
    实现代码:
#import "ViewController.h"
#import 
@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意思是旋转的方向,当添加了以上两个方法你会发现并没什么用,看看官方文档:

iPad横竖屏适配_第3张图片

过描述我们可以发现这两个方法需要设置在根视图中,意思是说确实这个页面是否旋转旋转的方向都是要根据根视图的这两个方法来判断,如果有导航控制器,在 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;
}

结果:

iPad横竖屏适配_第4张图片

如上使用的是系统的 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;
}

结果:

iPad横竖屏适配_第5张图片

可看到横屏下右侧有空白,是因为横屏是的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 ()
@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适配横竖屏设置

可进行如下设置:

iPad横竖屏适配_第6张图片

iPad横竖屏适配_第7张图片

我注意到有些项目中可以直接分别设置iphone和iPad的横竖屏、状态栏样式,但是我新建的项目中并没有,网上也没有找到资料,知道的读者麻烦说下,如图:

iPad横竖屏适配_第8张图片

最后

1、继承自UIView的类设置子控件frame最好在layoutSubViews方法,继承自UIViewControllerUITableViewControllerUICollectionController的控制器布局最好在viewWillLayoutSubviews里面,这样横竖屏切换不用再设置frame
2、 willRotateToInterfaceOrientation:duration:在ios8之后已经废弃,建议使用viewWillTransitionToSize: withTransitionCoordinator:

1、在viewWillTransitionToSize: withTransitionCoordinator:方法中获取屏幕宽高:[UIScreen mainScreen].bounds.size.width,这个方法是在屏幕旋转之前执行的,按理说此时屏幕宽高是旋转之前的屏幕宽高,但是某些情况下会出问题,问题截图:

iPad横竖屏适配_第9张图片

问题的四个状态:
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)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    NSLog(@"%f           %f", [UIScreen mainScreen].bounds.size.width, kSrceenW);
}```
根据打印结果我们会发现情况2、3、4三种情况打印的是旋转之前的屏幕宽度,情况1打印的是旋转之后屏幕宽度,坑,所以我们最好用方法参数`size`(控制器view的size)对屏幕进行适配

你可能感兴趣的:(iPad横竖屏适配)