注册

iOS多设备适配简史以及相应的API支撑实现

远古的iPhone3和iPhone4时代,设备尺寸都是固定3.5inch,没有所谓的适配的问题,只需要用视图的frame属性进行硬编码即可。随着时间的推移,苹果的设备种类越来越多,尺寸也越来越大,单纯的frame已经不能简单解决问题了,于是推出了AutoLayout技术和SizeClasses技术来解决多种设备的适配问题。一直在做iOS开发的程序员相信在下面的两个版本交界处需要处理适配的坎一定让你焦头烂额过:

1、iOS7出来后视图控制器的根视图默认的尺寸是占据整个屏幕的,如果有半透明导航条的话也默认是延伸到导航栏和状态栏的下面。这段时间相信你对要同时满足iOS7和以下的版本进行大面积的改版和特殊适配处理,尤其是状态栏的高度问题尤为棘手。

2、iOS11出来后尤其是iPhoneX设备推出,iPhoneX设备的特殊性表现为顶部的状态栏高度由20变为了44,底部还出现了一个34的安全区,当横屏时还需要考虑左右两边的44的缩进处理。你需要对所有的布局代码进行重新适配和梳理以便兼容iPhoneX和其他设备,这里面还是状态栏的高度以及底部安全区的的高度尤为棘手。

个人认为这两个版本的发布是iOS开发人员遇到的需要大量布局改版的版本。为了达到完美适配我们可能需要写大量的if,else以及写很多宏以及版本兼容来进行特殊处理。当然苹果也为上面两次大改版提供了诸多的解决方案:

1、iOS7中对视图控制器提供了如下属性来解决版本兼容性的问题:

@property(nonatomic,assign) UIRectEdge edgesForExtendedLayout NS_AVAILABLE_IOS(7_0); // Defaults to UIRectEdgeAll
@property(nonatomic,assign) BOOL extendedLayoutIncludesOpaqueBars NS_AVAILABLE_IOS(7_0); // Defaults to NO, but bars are translucent by default on 7_0.
@property(nonatomic,assign) BOOL automaticallyAdjustsScrollViewInsets API_DEPRECATED_WITH_REPLACEMENT("Use UIScrollView's contentInsetAdjustmentBehavior instead", ios(7.0,11.0),tvos(7.0,11.0)); // Defaults to YES

@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide API_DEPRECATED_WITH_REPLACEMENT("-[UIView safeAreaLayoutGuide]", ios(7.0,11.0), tvos(7.0,11.0));

2、iOS11中提出了一个安全区的概念,要求我们的可操作视图都放置在安全区内,并对视图和滚动视图提供了如下扩展属性:

@property (nonatomic,readonly) UIEdgeInsets safeAreaInsets API_AVAILABLE(ios(11.0),tvos(11.0));
- (void)safeAreaInsetsDidChange API_AVAILABLE(ios(11.0),tvos(11.0));

/* The top of the safeAreaLayoutGuide indicates the unobscured top edge of the view (e.g, not behind
the status bar or navigation bar, if present). Similarly for the other edges.
*/
@property(nonatomic,readonly,strong) UILayoutGuide *safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
/* When contentInsetAdjustmentBehavior allows, UIScrollView may incorporate
its safeAreaInsets into the adjustedContentInset.
*/
@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset API_AVAILABLE(ios(11.0),tvos(11.0));

/* Also see -scrollViewDidChangeAdjustedContentInset: in the UIScrollViewDelegate protocol.
*/
- (void)adjustedContentInsetDidChange API_AVAILABLE(ios(11.0),tvos(11.0)) NS_REQUIRES_SUPER;

/* Configure the behavior of adjustedContentInset.
Default is UIScrollViewContentInsetAdjustmentAutomatic.
*/
@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior API_AVAILABLE(ios(11.0),tvos(11.0));

/* contentLayoutGuide anchors (e.g., contentLayoutGuide.centerXAnchor, etc.) refer to
the untranslated content area of the scroll view.
*/
@property(nonatomic,readonly,strong) UILayoutGuide *contentLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));

/* frameLayoutGuide anchors (e.g., frameLayoutGuide.centerXAnchor) refer to
the untransformed frame of the scroll view.
*/
@property(nonatomic,readonly,strong) UILayoutGuide *frameLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));

这些属性的具体意义这里就不多说了,网络上以及苹果的官方都有很多资料在介绍这些属性的意思。从上面的这些属性中可以看出苹果提出的这些解决方案其主要是围绕解决视图和导航条、滚动视图、状态栏、屏幕边缘之间的关系而进行的。因为iOS7和iOS11两个版本中控制器中的视图和上面所列出的一些内容之间的关系变化最大。

NSLayoutConstraint约束以及iOS9上的封装改进
在iOS6时代苹果推出了AutoLayout的技术解决方案,这是一套采用以相对约束来替代硬编码的解决方法,然而糟糕的方法名和使用方式导致使用成本和代码量的急剧增加。比如下面的一段代码:

UIButton *button = [self createDemoButton:NSLocalizedString(@"Pop layoutview at center", "") action:@selector(handleDemo1:)];
button.translatesAutoresizingMaskIntoConstraints = NO; //button使用AutoLayout
[scrollView addSubview:button];

//下面的代码是iOS6以来自带的约束布局写法,可以看出代码量较大。
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];

[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeTop multiplier:1 constant:10]];

[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:40]];

[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeWidth multiplier:1 constant:-20]];


一个简单的将按钮放到一个UIScrollView中去的代码,当用AutoLayout来实现时出现了代码量风暴问题。对于约束的设置到了iOS9以后有了很大的改进,苹果对约束的设置进行了封装,提供了三个类:NSLayoutXAxisAnchor, NSLayoutYAxisAnchor, NSLayoutDimension来简化约束的设置,还是同样的功能用新的类来写约束就简洁清晰很多了:

UIButton *button = [self createDemoButton:NSLocalizedString(@"Pop layoutview at center", "") action:@selector(handleDemo1:)];
button.translatesAutoresizingMaskIntoConstraints = NO; //button使用AutoLayout
[scrollView addSubview:button];
[button.centerXAnchor constraintEqualToAnchor:scrollView.centerXAnchor].active = YES;
[button.topAnchor constraintEqualToAnchor:scrollView.topAnchor constant:10].active = YES;
[button.heightAnchor constraintEqualToConstant:40].active = YES;
[button.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor multiplier:1 constant:-20].active = YES;

UIStackView
在iOS9中还提供了一个UIStackView的类来简化那些视图需要从上往下或者从左往右依次添加排列的场景,通过UIStackView容器视图的使用就不再需要为每个子视图添加冗余的依赖约束关系了。在大量的实践中很多应用的各板块其实都是按顺序从上到下排列或者从左到右排列的。所以如果您的应用最低支持到iOS9的话就可以大量的应用这个类来构建你的程序了。

占位视图类UILayoutGuide
在iOS9以前两个视图之间的间距和间隔是无法支持浮动和可伸缩设置的,以及我们可以需要在两个视图之间保留一个浮动尺寸的空白区域,解决的方法是在它们中间加入一个透明颜色的UIView来进行处理,不管如何只要是View都需要进行渲染和绘制从而有可能一定程度上影响程序的性能,而在iOS9以后提供了一个占位视图类UILayoutGuide,这个类就像是一个普通的视图一样可以为它设置约束,也可以将它添加进入视图中去,也可以将这个占位视图作为其他视图的约束依赖项,唯一的不同就是占位视图不会进行任何的渲染和绘制,它只会参与布局处理。因此这个类的引入可以很大程度上解决那些浮动间距的问题。

SizeClasses多屏幕适配
当我们的程序可能需要同时在横屏和竖屏下运行并且横屏和竖屏下的布局还不一致时,而且希望我们的应用在小屏幕上和大屏幕上(比如iPhone8 Plus 以及iPhoneX S Max)的布局有差异时,我们可能需要用到苹果的SizeClasses技术。这是苹果在iOS8中推出来的一个概念。 但是在实际的实践中我们很少有看到使用SizeClasses的例子和场景以及在我们开发中很少有使用到这方面的技术,所以我认为这应该是苹果的一个多屏幕适配的失败解决的方案。从字面理解SizeClasses就是尺寸的种类,苹果将设备的宽和高分为了压缩和常规两种尺寸类型,因此我们可以得到如下几种类型的设备:

0a40fab08846e6d070b17a14a4be23c5.png

很欣慰的是如果您的应用是一个带有系统导航条的应用时很多适配的问题都能够得到很好的解决,因为系统已经为你做了很多事情,你不需要做任何特殊的处理。而如果你的应用的某个界面是present出来的,或者是你自己实现的自定义导航条的话,那么你可能就需要自己来处理各种版本的适配问题了。并且如果你的应用可能还有横竖屏的话那这个问题就更加复杂了。

最后除了可以用系统提供的API来解决所有的适配问题外,还向大家推荐我的开源布局库:MyLayout。它同时支持Objective-C以及Swift版本。而且用这个库后上面的所有适配问题都不是问题。

转自:https://www.jianshu.com/p/b43b22fa40e3

0 个评论

要回复文章请先登录注册