iPhoneX适配
1. 屏幕大小
iPhoneX屏幕大小为375x812个点,相比于iPhone6和7的375x667个点 在垂直方向上多出145个点,需要注意的是坐标系的原点为方框的左上角,也就意味着如果代码给控件设置绝对frame有可能会被四周圆角或者顶部的摄像头区域给切掉。 图1坐标系.png2. 各种高度改变
iPhoneX的状态栏高度由之前的20个点变为44个点(如图2状态栏和导航栏) 图2状态栏和导航栏.png导航栏的高度默认情况下还是44,iOS11之后新增大标题模式,largeTitle开启的情况导航栏高度是96,除去原有的高度在下面多出了52个点高度的显示大标题的地方。(图3大标题模式) 图3大标题模式.png
tabBar的高度没有变 依旧是49,不过在tabBar下面还有一个高度为34的带圆角的区域,这个区域中包含HomeIndicator提供给用户返回Home和唤出多任务的功能。
3. safeArea
iOS11新增safeArea的概念就是苹果为了给开发者提供一个适配和开发的准则, 安全区域就是能够正常有效的显示视图并和用户交互的区域,区域大小并不是固定的 而是根据控制器否有导航栏、状态栏、tabBar、ToolBar动态改变的。 原则上所有非滚动试图都应该显示在安全区域内以保证正确的显示和交互。竖屏安全区域 (图4竖屏安全区域) 图4竖屏安全区域.png和横屏下的安全区域 (图5横屏安全区域) 图5横屏安全区域.png
4. 检查自己的应用哪些地方需要适配
Xcode9带有iPhoneX的模拟器,可以在上面运行查看自己应用的显示情况,如果发现自己的app在X下运行时上下都有黑条,那是因为应用在启动的时候没有对应的启动图,所以需要添加在X下运行的@3X的1125X2436px大小的启动图,准备好启动图后showInFinder放到启动图文件夹下,然后在Contents.json中添加对应的描述即可
{
"extent" : "full-screen",
"idiom" : "iphone",
"subtype" : "2436h",
"filename" : "0b1@2x-1.png",
"minimum-system-version" : "11.0",
"orientation" : "portrait",
"scale" : "3x"
},
注意filename是你启动图的名字,如果添加后Contents.json的格式不正确,可以新建一个lanchImage,新建的lanchImage文件夹下的Contents.json带有iPhoneX启动图的描述, 然后在新建的lanchImage中找到Contents.json文件中的相关描述复制过来即可。
5. 界面的适配
应用大部分都是竖屏界面,先说说竖屏界面的适配.
-
对于使用系统的导航控制器和TabBar控制器的话,运行在X上的时候系统会自动把背景拉伸到合适的位置,以适应X的显示。如果是自定义的Navigation或则TabBar可能就需要去适配了。 判断是否是X来让Navigationbar和TabBar显示在合适的位置。
-
对于纯代码实现的view,因为参照的固定高度在iPhoneX下发生了改变,可能会出现内容显示在安全区域之外的情况。所以要区别iPhoneX与其他设备来动态的改变控件的布局,为此定义了几个宏,因为控制器中的需要适配的view可能不止一个,所以如果一个一个去改frame的话太麻烦,我的思路是先添加一个安全区域大小的contentView,然后让之前所有的控件都加在contentView上,这样会减少部分代码修改,并且如果之后再修改的会简单点。
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
#define iPhoneX (ScreenWidth == 375.f && ScreenHeight == 812.f ? YES : NO)
#define StatusBarHeight (iPhoneX ? 44.f : 20.f)
#define NavigationBarHeight 44.f
#define TabbarHeight (iPhoneX ? (49.f+34.f) : 49.f)
#define TabbarSafeBottomMargin (iPhoneX ? 34.f : 0.f)
#define LanscapeLeftMargin (iPhoneX ? 44.f : 0.f)
#define LanscapeRightMargin (iPhoneX ? 44.f : 0.f)
#define LanscapeBottomMargin (iPhoneX ? 0.f : 0.f)
- xib的适配,如果项目的最低部署版本是9.0以上,可以直接在xib文件的描述设置中勾选使用安全区域布局(如图6xib打开safeArea)这样文件会自动添加一个安全区域,然后在需要适配的地方(比如上下左右)改为约束参照safeArea即可 系统会自动调整显示内容。如果最低部署版本是9.0以下,不能使用safeArea, 勾选之后编译报错,这时只能自己去调整了。 我自己适配的思路还是添加一个contentView,给contentView添加约束大小为安全区域的大小, 把所有控件都添加到contentView上面重新设置约束保持原来的布局, 然后在代码中根据机型动态调整contentView的约束。 图6xib打开safeArea.png
6. 导航栏的适配
在iOS11下导航栏左边和右边item的位置发生了变化,左边的往右移动了一段距离,而右边的往左移动了一段距离,好像是20个点。而在iOS11之前内边距也是存在的,但是大部分都是加了一个负距离的FixedSpace的适配好了。但是在iOS11中 导航栏的结构发生了变化,(如图7导航栏结构)在iOS11下就会出现偏移距离的问题。怎么解决呢? 图7导航栏结构.png-
给leftButton设置x值为-20 ? 测试后发现ButtonBarStackView的大小和button的大小一样,但是位置没有变化。并且即使这样button的位置起作用了,超出父控件的部分不能接收事件。
-
能不能在控制器的view显示之前遍历navigationbar的子控件拿到UIButtonBarStackView,然后改变其约束呢? 我把遍历的代码写在viewWillAppear中 发现遍历后打印的数组是空的,放在didAppear中遍历就有了,但是这样不行 因为在DidAppear中重新设置UIButtonBarStackView的位置的话,控制器显示后可以看到button的移动 🤣
-
可以设置button的内容偏移contentEdgeInsets 让按钮的内容往左或者往右偏移20 的距离,但是会出现用户点击返回按钮但是没有反应的情况。可以重写navigationBar 的hitTest方法 判断点击的位置如果是左边的button就让左边的button接受事件,右边的同理。怎样重写UINavigationBar的方法呢 一种是继承然后重写,但是这种明显不适合系统控件,第二种就是给UINavigationBar添加分类 然后在分类中重写hitTest方法 然后判断点击的位置是左边的还是右边的,返回对应的button即可
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event];
if (point.x < [UIScreen mainScreen].bounds.size.width * 0.5) {
for (UIBarButtonItem *item in self.topItem.leftBarButtonItems) {
if (item.customView == nil) {
continue;
}
if ([item.customView isKindOfClass:[HitButton class]]) {
HitButton *leftButton = (HitButton *)item.customView;
CGRect newRect = [leftButton convertRect:leftButton.hitFrame toView:self];
if (CGRectContainsPoint(newRect, point)) {
view = leftButton;
break;
}
}
}
}else{
for (UIBarButtonItem *item in self.topItem.rightBarButtonItems) {
if (item.customView == nil) {
continue;
}
if ([item.customView isKindOfClass:[HitButton class]]) {
HitButton *rightButton = (HitButton *)item.customView;
CGRect newRect = [rightButton convertRect:rightButton.hitFrame toView:self];
if (CGRectContainsPoint(newRect, point)) {
view = rightButton;
break;
}
}
}
}
return view;
}
7. tableView的适配
- 在iOS11之后,UIViewController的automaticallyAdjustsScrollViewInsets属性被废弃,取而代之的是给UIScrollView添加了contentInsetAdjustmentBehavior属性 该属性枚举类型,控制器当scrollView靠近状态栏或者导航栏时content调整的策略, 默认是自动调整,这样如果scrollView出现在状态栏或者导航栏下面 在iOS11下面会自动调整内容向下偏移,如果不想偏移的话可以设置contentInsetAdjustmentBehavior的属性为never即可
- 下面的一堆属性默认都是自适应了 所以如果发现tableView显示出现异常,设置对应的属性为0或者在代理方法中返回0即可
@property (nonatomic) CGFloat rowHeight; // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat sectionHeaderHeight; // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat sectionFooterHeight; // default is UITableViewAutomaticDimension
@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
@property (nonatomic) CGFloat estimatedSectionHeaderHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
@property (nonatomic) CGFloat estimatedSectionFooterHeight NS_AVAILABLE_IOS(7_0); // default is UITableViewAutomaticDimension, set to 0 to disable
- 横屏下tabView会自动调整其contentView的位置以显示在安全区域中,所以如果是纯代码自定义的cell,务必保证添加子控件到contentView上
- 对于像tableVie和collectionView这样的流式布局在底部的约束苹果建议是到屏幕底部,不应约束在safeArea中 系统中的应用也是这样做的 像设置界面 homeIndicator就是在tableViewCell的上面,但是不影响tableView的交互,点击的话可以正常响应事件,并且上划的回到home
8. 视频横屏播放适配
-
homeIndicator的隐藏
原则上不要隐藏homeIndicator 并且不要在其附近摆放控件 设置交互 以免和系统的交互冲突。特殊状态下需要隐藏homeIndicator , 比如视频横屏播放 或者播放ppt等需要全屏显示的时候。和状态栏的隐藏类似 ,在控制器中 实现方法 - (bool)prefersHomeIndicatorAutoHidden 但是和隐藏状态栏不同是 即使该方法返回的是YES ,homeIndicator也不会立即隐藏 而是交给系统去判断,当系统认为时机合适时给与隐藏(比如控制器一段时间内没有事件交互)不合适的时候(有事件交互) 还是会显示出来 -
播放画面填充
因为iPhoneX屏幕比例不同于以往的16:9, 画面不能按照正常比例充满屏幕, 视频横屏播放的时候有两种填充方案,一种是AspectFit ,这种画面按比例显示完整,但是会有部分屏幕没有显示画面。另外一中方式AspectFill, 按比例充满,但是会切割部分画面,苹果鼓励应用把两个选项提供给用户,让用户选择画面的填充模式。
图8aspectToFit.png 图9aspectToFill.png
- 边缘手势冲突
注意如果你的应用在下边缘有手势触发的交互一定要和homeIndicator区别开,如果一定要在下边缘添加手势的话可以开启EdgeProtection 开启边缘保护后 第一次轻扫会唤醒homeIndicator并且激活控件,当第二次轻扫的时候才会唤醒多任务界面。但是苹果建议不是特殊情况不要这样使用,因为会影响用户体验