全面屏刚出时,网上有说反人类。但过去这么久了,趋于技术的进步或看久了,你们也都慢慢习惯了(只是笔者仍是买不起全面屏)。官方适配中文版文档也出来了。html
图源:( baijiahao.baidu.com/s?id=157902… )ios
回想起刚开始适配全面屏用了一种暴力、并不优雅的方法,以致于后来出了XS(MAX)和XR后出了bug。因此选择一种可靠的、优雅的方案是颇有必要的。现在网上关于探讨适配全面屏的文章五花八门,笔者将探究其中的各类方案。安全
因为笔者水平有限,眼界狭窄,不免出现疏忽的地方,但愿大神提出更好的方案。bash
从以上两图,咱们能够看出全面屏的顶部Statusbar
变高了,其余部分没变。微信
Largetitle
是iOS11中新加入的特性。固然咱们开发中不多用到Largetitle。app
全面屏底部多出了高度为34的Home Indicator 区域。iphone
虽然笔者买不起XStyle,可是虚拟器应该能知足适配的全部测试。因此开发中,请优先使用全面屏开发。笔者有个朋友,开发时用非全面屏,偶尔会出现忘记适配全面屏问题。若是用全面屏,开发效率将会进一步提高。毕竟界面适配全面屏的时候,很难忘记适配非全面屏。ide
App显示界面大小是由App启动页决定的。布局
记得iPhoneX刚出时,App在其上面运行显示居中,大小和6s同样,上下各有一块黑块。尝试打印出分辨率惊奇发现不是官方宣传的1124,2436。把启动页大小改了宏才达到预想效果。若是用xib,那就没什么问题。启动页用图片的话,要适配上@3x的图片。测试
宏里只要能区分开XStyle,其余高度就好说。
网上不少教程都是按照分辨率来区分。然而,根据上面咱们能够发现,XStyle的分辨率并不是固定。因此单纯按照分辨率是不行的。笔者一开始适配X就是这样,后来XSMax出了问题,被迫强行更新XCode10用XSMax,发现宏没写好(顺便吐槽一句,XCode10真是噩梦,又懒得下回去)。
也有旧教程是按照屏幕宽高。但根据上面数据也不是固定的,因此要注意。这两种失效宏都列在下面。
// 单纯根据分辨率
#define K_iPhoneXStyle ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
// 单纯根据屏幕宽高
#define K_iPhoneXStyle (KScreenWidth == 375.f && KScreenHeight == 812.f ? YES : NO)
复制代码
其实笔者很想弄出一个通用的宏,不那么怕出新机会失效的宏,但奈何实在想不到。只能照XStyle不同的数据写出宏。下面列出的宏在XS Max时仍是有效的。至于之后就说不许了,各位仍是要根据新机分辨率或者宽高适当修改。
#define K_iPhoneXStyle ( (CGSizeEqualToSize(CGSizeMake(414, 896), [[UIScreen mainScreen] bounds].size)) || ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO) )
复制代码
或者
#define K_iPhoneXStyle ((KScreenWidth == 375.f && KScreenHeight == 812.f ? YES : NO) || (KScreenWidth == 414.f && KScreenHeight == 896.f ? YES : NO))
复制代码
还有其余的宏
#define KScreenWidth ([UIScreen mainScreen].bounds.size.width)
#define KScreenHeight ([UIScreen mainScreen].bounds.size.height)
#define K_iPhoneXStyle ((KScreenWidth == 375.f && KScreenHeight == 812.f ? YES : NO) || (KScreenWidth == 414.f && KScreenHeight == 896.f ? YES : NO))
#define KStatusBarAndNavigationBarHeight (K_iPhoneXStyle ? 88.f : 64.f)
#define KStatusBarHeight (K_iPhoneXStyle ? 44.f : 20.f)
#define KTabbarHeight (K_iPhoneXStyle ? 83.f : 49.f)
#define KMagrinBottom (K_iPhoneXStyle ? 34.f : 0.f)
复制代码
还有些宏,是适配字体或者view用的。将在后面介绍UI在不一样尺寸下适配方案再提起。
#define KScaleWidth(width) ((width)*(KScreenWidth/375.f))
#define IsIphone6P SCREEN_WIDTH==414
#define SizeScale (IsIphone6P ? 1.5 : 1)
#define kFontSize(value) value*SizeScale
#define kFont(value) [UIFont systemFontOfSize:value*SizeScale]
复制代码
最后说一句,利用宏来写虽然简单,但有如下弊端。
即便有弊端,笔者仍是以为这么写可行。毕竟大多数App都不用支持横屏,并且屏幕短期内不会有太大变更。
人要保持一颗活到老学到老的心,这些弊端有方法避免。
iOS11出了安全区域SafeArea这个概念,用得好能够解决以上问题。要点时间适应。
弊端是目前大多App都支持iOS11.0-,这样就要写判断版本号,代码多将近一倍。
优势也很明显
若是苹果新出了新机型,不用改动代码适应的可能性很是大。这意味着不用为了适配问题上线新版本。
横屏时顶部不会有偏移。有横屏需求的话,也许就不用为了横屏作额外适配。
等之后App iOS11.0起步的时候(短期不太可能qaq),我的感受SafeArea将会成为主流。
但至少目前,看上去很美好,实际上适配写多一倍的代码让笔者望而生畏。
iOS7之后,苹果给UIViewController
引入了topLayoutGuide 和 bottomLayoutGuide两个属性。用于表示顶部或底部的高度。根据有无显示状态栏、导航栏、tabBar返回高度。若是VC内嵌VC,内嵌的VC将视为另起的顶底坐标体系,不受原状态栏等影响。你可能听都没听过这两属性,由于开发中咱们习惯直接用宏写上数值,几乎不用这两个属性。更况且那时iPhone尚未全面屏这种东西。
到了iOS11,苹果弃用了topLayoutGuide和bottomLayoutGuide两个属性。引入了safeArea代替。官方的建议是 不能被遮挡的内容和控件在安全区域范围内显示。若是视图底部有按钮,在全面屏下,请约束底部距离34,不要影响到Home功能。
此属性适用于自动布局。
使用前
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
复制代码
使用后
[NSLayoutConstraint constraintWithItem:someView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view.safeAreaLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
复制代码
笔者用的是Masonry。注意该属性是iOS11后出现的。由于X发布时,最低版本超过了11,因此全面屏都能用此属性。在这里能够看出,由于11不支持,这代码多了一倍。咱们彻底能够不用这新属性,减小一半代码爽歪歪。因此笔者目前开发还未使用。
[testView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@44);
if (@available(iOS 11.0,*)) {
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft);
make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight);
} else {
make.top.equalTo(self.view).offset(KStatusBarHeight);
make.left.equalTo(self.view);
make.right.equalTo(self.view);
}
}];
复制代码
笔者没有全面屏,只能展现一下热点了。该效果与用宏KStatusBarHeight
同样。
(注:此手机系统12.0)
但横屏时就不同了。宏写法会与上方有一段KStatusBarHeight距离,此方法没有。这就是笔者说的其中一个优势,然而并无好到足够让笔者用它的程度。
最后有两个观点。
1⃣️对于在ViewController的view,推荐使用mas_safeAreaLayoutGuide。这样就能动态更改,即便横屏。
2⃣️对于在View之间的约束,推荐使用mas_left。一来不必用safeArea,二来不用判断版本号,减小代码量。
此属性适用于手动计算frame。
X竖屏时控制器view的safeAreaInsets是(44,0,34,0);横屏(0, 44, 21, 44)。
用到的是这个方法- (void)viewSafeAreaInsetsDidChange;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, KStatusBarHeight, KScreenWidth, 200)];
testView.backgroundColor = [UIColor blackColor];
[self.view addSubview:testView];
self.testView = testView;
}
- (void)viewSafeAreaInsetsDidChange {
[super viewSafeAreaInsetsDidChange];
NSLog(@"%s", __func__);
[self updateFrame];
}
- (void)updateFrame {
if (@available(iOS 11.0, *)) {
CGRect newFrame = self.testView.frame;
newFrame.origin.x = self.view.safeAreaInsets.left;
newFrame.size.width = KScreenWidth - self.view.safeAreaInsets.left - self.view.safeAreaInsets.right;
self.testView.frame = newFrame;
}
}
复制代码
用frame的话,不只低于11的系统,就连高于11的系统,要适配起横屏问题都比较麻烦。
因此笔者是趋向于用约束的,见仁见智吧。
这里再贴出两篇说SafeArea的文章。
笔者好久不用xib了,贴出现成的一篇文章。
automaticallyAdjustsScrollViewInsets:
在iOS7.0之后,相对于ScrollView新增属性,默认为YES,系统会根据所在界面的astatus bar, search bar, navigation bar, toolbar, or tab bar等自动调整ScrollView的inset。
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"我是导航条";
self.view.backgroundColor = [UIColor redColor];
UITableView *testTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight) style:UITableViewStylePlain];
testTableView.backgroundColor = [UIColor blueColor];
testTableView.delegate = self;
testTableView.dataSource = self;
[self.view addSubview:testTableView];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
cell.textLabel.text = [NSString stringWithFormat:@"%ld", (long)indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
复制代码
能够看到没有改变tableView的frame,只是显示范围变了。
若是没有这个属性,咱们要实现一样的效果,tableView尺寸要这样设置。固然手动修改insets也是能够的。
x = 0,
y = KStatusBarAndNavigationBarHeight,
width = KScreenWidth,
height = KScreenHeight - KStatusBarAndNavigationBarHeight
复制代码
可是要注意:这种自动调整是在ScrollView是其根视图添加的的第一个控件的时候,才会出现自动调整的效果。详情查看automaticallyAdjustsScrollViewInsets属性
iOS11中废弃了automaticallyAdjustsScrollViewInsets,取而代之的是contentInsetAdjustmentBehavior属性。
该属性原理和automaticallyAdjustsScrollViewInsets原理类似,是为了进一步适应安全区域。
若是你须要自定义内边距,代码将变成如下这样。
if (@available(iOS 11.0, *)) {
self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
复制代码
contentInsetAdjustmentBehavior各个值之间的区别
连接里有这观点
最后来谈一下关于全面屏的适配方案。
safeAreaLayoutGuide
约束将很是刺激。横屏基本不用管,除非排版需求不同。safeAreaLayoutGuide
反而要多判断版本号。看之后App支持版本号趋势吧。有电话打进来、手机开了热点有链接,状态栏会变长20。虽然不少App并未对这些状况适配,但优秀的App应该要处理好。
横屏和电话热点并没有直接关系。横屏状态下默认状态栏是不显示的。
当状态栏增高时,App的控制器的view将会下移20,可是高度却不变。tabBar不会有任何改变。因此若是某个界面scrollView一直到底的话,最好用约束到底部,这样调用viewWillLayoutSubviews时就会修改scrollView高度。建议不要写高度,否则会出现scrollView底部显示丢失20高度的问题。 iOS 热点 拨打电话 适配
这时就要说回上面提到的宏了。
#define KScaleWidth(width) ((width)*(KScreenWidth/375.f))
#define IsIphone6P SCREEN_WIDTH==414
#define SizeScale (IsIphone6P ? 1.5 : 1)
#define kFontSize(value) value*SizeScale
#define kFont(value) [UIFont systemFontOfSize:value*SizeScale]
复制代码
相信开发过程当中都遇到过字体适配问题。
UIFont
App若是要设置全局字体,能够经过Swizzing修改。或者像以上宏同样,传进参数,修改字体大小。
看过一篇文章,淘宝在Plus机型的字体都加大成1.5倍。笔者买不起Plus机型,更不敢装淘宝这种剁手App,因此没法验证。
本文要探究的是UILabel显示的问题。重点并不在UIFont上。
先研究UILabel。
numberOfLines
只有设定了宽度约束的状况下起效。不然Label只会显示一行。textAlignment
能够设置。
lineBreakMode
设置文字过长时省略号放哪。label.lineBreakMode = NSLineBreakByCharWrapping;以字符为显示单位显
示,后面部分省略不显示。
label.lineBreakMode = NSLineBreakByClipping;剪切与文本宽度相同的内
容长度,后半部分被删除。
label.lineBreakMode = NSLineBreakByTruncatingHead;前面部分文字
以……方式省略,显示尾部文字内容。
label.lineBreakMode = NSLineBreakByTruncatingMiddle;中间的内容
以……方式省略,显示头尾的文字内容。
label.lineBreakMode = NSLineBreakByTruncatingTail;结尾部分的内容
以……方式省略,显示头的文字内容。
label.lineBreakMode = NSLineBreakByWordWrapping;以单词为显示单位显
示,后面部分省略不显示。
复制代码
adjustsFontSizeToFitWidth
//设置字体大小适应label宽度minimumScaleFactor
sizeToFit
改变Label的尺寸以显示文字。须要注意的是,须要在label.text赋值后执行。若是宽高都进行了约束,那么调用sizeToFit将无效果。若是只约束了宽度,而且行数非1,那么sizeToFit会修改Label的高度;若是只约束了高度,或者行数为1,那么sizeToFit只会修改Label的宽度。若是两者皆未约束,只会修改Label宽度。
sizeToFit
和adjustsFontSizeToFitWidth
的区别。从字面上咱们就能区分开,前者是改变Label的宽高,后者是改变字体大小。
反正之后作项目的时候,明确需求,咱们是固定了字体的大小来适配label的宽,仍是固定了label的宽来适配字体的大小,前者用sizeToFit,后者用adjustFontsToFit。
以淘宝举例。为了研究只能下这剁手App了。
se下搜iPhoneX
6s下搜iPhoneX
这里无视Label左边的图标(天猫、双11)。
其商品标题有两行。以前提到了,若是不做处理,Label默认垂直方向居中。若是文字长度只有一行,那会显示奇怪。因此笔者来约束的话,先作垂直方向处理,而且约束了宽度距离左边图片,距离cell.contentView右边。而后设置行数为2(或者约束高度,行数为0,lineBreakMode裁剪)。
还有一种方案是Label根据文字长度自动适配高度,并设置最大高度限制。输入框高度就用相似方案。网上搜UITextView自动高度一堆教程。笔者懒得翻就不弄了。基本原理是根据文字大小长度、Label的宽度、文字间距,算出文字高度,而后设置Label高度为 最大限制高度与文字高度 较小者。
再来看价格和付款人数Label。
笔者设计的话,会采用如下形式。
priceLabel.font = ...
[priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(imageView.mas_right).offset(...);
make.height.equalTo(...);
make.top...;
}];
numberOfPeopleLabel.font = ...
[numberOfPeopleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(priceLabel.mas_right).offset(...);
make.height.equalTo(...);
make.top...;
}];
复制代码
而后在cell设置model时
priceLabel.text = ...
[priceLabel sizeToFit];
numberOfPeopleLabel.text = ...
[numberOfPeopleLabel sizeToFit];
复制代码
总结一下,开发中不多会用到adjustsFontSizeToFitWidth
,大多数时候都会顶部对齐、换行、裁剪,或设置自动高度。
来讲最后一个很是有用的宏。
#define KScaleWidth(width) ((width)*(KScreenWidth/375.f))
复制代码
笔者遇到的设计师给的图都是i6屏幕,其宽度为375.f。若是给的图不是,那么将这个宏数值修改便可。
这个宏有什么用呢?
其实就是一个比例转换的问题。不一样屏幕下,某些UI可能大小不同,这时候采用这个宏将会很是方便。
仍是举回上面的两张淘宝图例子。
笔者目测(目测而已),cell是不一样高度的。假如6s中商品图的宽度150.f,占屏宽0.4。在se中按照比例,320 * 0.4,为128。
那么咱们用这个宏,就能一步到位。KScaleWidth(150),在6s中就为150,在se中为128。
除此以外,间距约束用这个宏也有奇效。
这个宏在collectionView中更显神威。
设计图算好了两个cell的间距,每一个cell的大小,整个collectionView的大小,contentInset。这时咱们采用这个宏,在不一样屏幕下的适配问题将迎刃而解。
这里为何不写出KScaleHeight呢?
笔者并非说不能用,只是view一般是被宽度所限制。你见过微信的cell文本内容高度有变化吗hhhhh。就像图片尺寸变了,高度也是被图片宽度带动,而不是屏幕高度。
固然此宏虽颇有用,可是开发中仍是要通过考虑哪些地方须要用。