一、监听横竖屏的切换
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)对屏幕进行适配