034-iOS屏幕适配-iOS笔记

/ 0

学习目标

1.【了解】屏幕适配的发展史

2.【了解】autoResizing基本用法

3.【掌握】autoLayout 的基本用法

4.【掌握】autoLayout代码实现

5.【理解】sizeClass的基本用法

一、屏幕适配的发展史

随着iOS屏幕尺寸越来越多样化,屏幕适配也就越来越重要了。

iphone1 - iphone3gs时代,window的size固定为(320,480)。我们只需要简单计算一下相对位置就好了,不需要做屏幕适配。

iphone4 - iphone4s时代,苹果推出了retina屏,但是给了码农们非常大的福利:window的size不变,所以也不需要做适配。

iphone5 - iphone5s时代,window的size变了(320,568)。这时AutoresizingMask派上了用场(为什么不用Autolayout? 因为还要支持ios5)。

iphone6+时代,window的width也发生了变化(相对5和5s的屏幕比例没有变化)。终于是时候抛弃AutoresizingMask改用Autolayout,因为不用支持ios5了,并且相对于屏幕适配的多样性来说autoresizingMask也已经过时了。

二、autoresizing基本用法

虽然autoresizing已经过时,但我们还是有必要了解一下的。autoResizing是苹果早期的屏幕适配的解决办法,iOS6之前完全可以胜任,因为苹果手机只有3.5寸的屏幕,在加上手机app很少支持横屏,所以iOS开发者基本不用怎么适配布局,所有的UI控件只要相对父控件布局就可以了,没错autoResizing就是一个相对于父控件的布局解决方法。autoResizing是UIView的属性,使用非常简单,但是功能远远没有autolayout那样强大。如果你的界面比较简单,要求的细节没有那么高,你也可以使用autoResizing去进行自动布局。特别注意autoResizing只能适用于控件和他父控件直接的关系,不能控制两个同层级的控件直接的关系

在使用autoResizing进行屏幕适配,可以通过Xcode可视化的界面调整或者代码去适配。在通过Xcode可视化界面调整去适配之前,需要先去掉下图中的两个勾选(autoLayout、sizeClass)。

Snip20150821_25

可以通过下图看到autoResizing通过可视化能调整的只有6根线刚好和它的6个枚举值对应。

Snip20150821_32

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0, 
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

外边的4根虚线用来设置当前view距离父控件的上、下、左、右的距离是否固定不变的。

内部的2根带箭头的线来设置view是否跟随父控件来自适应width和height。

代码则可以通过(view.autoresizingMask = 枚举值)来设定autoResizing值。

autoResizing的功能仅此而已,如果我们要限制两个子控件之间的关系,他就无能为力了。这样显然不够用!!!

三、autoLayout 的基本用法

做苹果开发的一个好处是有一个很好的东家(Apple公司),他不仅很注重用户体验,而且还不忘为开发者去除一些不必要的麻烦(例如:ARC的出现...)。

autoLayout:可以在任意两个控件之间建立布局关系,可以是父子view也可以是兄弟view等。功能强大了许多,当然学习成本也高了不少。

autoLayout的基础理论:

1.view具有自我计算尺寸,布局的能力。通过它自身的内容,能够得到尺寸。

2.view的布局位置,确定于它与superview及其他view之间的关系。

3.与传统的autoresizingmask自适应相比,autoLayout更精确,能绝对的确定view的布局。

4.view不一定需要一个初始的Rect。autoLayout中,view如果有足够的约束(constraint),便可以确定自己的尺寸和位置,并且知道自己和其他view的关系。也就是想确定view的布局,就给它(们)添加约束(constraint)。

在storyboard中使用autoLayout进行屏幕适配一定要勾选Use Auto Layout,和上面所演示的autoResizing相反。

Snip20150828_1

下面用一个案例来介绍在storyboard中使用autoLayout屏幕适配的基本操作。

我们需要适配两个等宽、等高的view之间的距离永远固定。autoResizing只能适用于控件和他父控件之间的关系,不能适用于同等级的两个控件之间的关系,所以我们必须使用autoLayout来实现。

勾选Use Auto Layout后,在storyboard布局区域底部有三个按钮,左数第一个是设置对齐方式相关的约束,具体如下图:

Snip20150828_2

左数第二个按钮是设置四个方向的约束和宽、高约束的,下图中有一个Constrain to margins选项,是如果我们的参考是父view的时候会屏幕边缘会有一个margin的宽度,不勾选者相当于屏幕边缘约束。

这里我让蓝色View的左、右距离父View边缘固定,并且高固定。这样得到蓝色View的(宽、x坐标、高)。还差一个y坐标,如果约束不齐全,请注意不要勾选下图绿色圈中的更新选项,保持None默认。

Snip20150828_3

当我点击Add 3 Constraints后,如下图所示,有红色箭头提示,标明目前控件的约束不全。并以虚线显示出目前约束会让View将改变的位置。

Snip20150828_5

选中要添加约束的控件,按住control键拖到参考控件也可以添加约束。这里我让蓝色View相当于父View垂直居中,这样y坐标也确认了,控件上的约束线都变成了蓝色,表示约束齐全并且都已经更新。如下图所示:

1

然后让紫色View和蓝色View左对齐、右对齐、等高,并让紫色View相当于父控件垂直居中。

2

修改两个View相当于父控件垂直居中对齐的偏移量一致,这样他们相对于父控件垂直居中的水平线(我也不知道咋形容,看下下图吧)距离相等。

3

下图就是说明垂直居中的偏移量的,水平居中也是同理。

Snip20150828_7

添加约束后完成的效果图,如下所示:

4

四、autoLayout代码实现

其实我们在storyboard中添加的每一条约束都是一个NSLayoutConstraint对象,也就是说我们上面的案例要用代码来实现的话,需要创建好多个NSLayoutConstraint对象。不过大神总是很多的,不需要担心开发中我们使用代码来进行屏幕适配会花大量时间精力去些适配代码,有很多大神已经写好了内裤,我们直接穿上就行了。推荐一个轻量级的布局框架Masonry,GitHub地址:https://github.com/SnapKit/Masonry。虽然大神的内裤我们可以直接穿,但是代码局部的我们还是必须掌握的,不懂原理怎么穿内裤呢?学习AutoLayout之前,必须要完全抛弃传统的frame属性,先完成思想的扭转学习起来方能事半功倍。AutoLayout是苹果ios6出来的东西,与传统的Frame属性不同。每一个view对象都有一个frame属性,frame属于CGrect对象,通过苹果的Api可以得知,CGrect其实是一个结构体。

struct CGRect {
    CGPoint origin;
    CGSize size;
};

而AutoLayout是通过约束来实现布局的。一个view一旦使用了AutoLayout约束,那么它的frame将永远都是0,所以在使用AutoLayout之前需要两个准备工作。

1.禁用autoResizing,因为autoresizing和autoLayout是不能共存的

设置translatesAutoresizingMaskIntoConstraints为NO。

2.创建约束对象并添加约束

1.如果参考是本身才添加到本身。

2.如果参考是父控件就添加到父控件。

3.如果参考是其他控件则添加到共同的父控件。

我们通过一个案例来演示代码实现autoLayout,我们的需求是实现两个View永远等高,永远右对齐,但其中一个View的宽度是另一个View宽度的一半。需求实现后的效果如下图所示:

5

ViewController.m

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建蓝色View并添加到父视图
    UIView *blueView = [[UIView alloc] init];
    blueView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:blueView];
    
    //创建红色View并添加到父视图
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    
    //禁用autoresizing
    blueView.translatesAutoresizingMaskIntoConstraints = NO;
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    
    //创建蓝色View的约束
    NSLayoutConstraint *blueTop = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:30];
    NSLayoutConstraint *blueLeft = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:30];
    NSLayoutConstraint *blueRight = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-30];
    NSLayoutConstraint *blueHeight = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:50];
    /*
     添加约束要注意的是:
     1.如果参考是本身才添加到本身。
     2.如果参考是父控件就添加到父控件。
     3.如果参考是其他控件则添加到共同的父控件。
     */
    //添加蓝色View的约束
    [self.view addConstraints:@[blueTop,blueLeft,blueRight]];
    [blueView addConstraint:blueHeight];
    
    //创建红色View的约束
    NSLayoutConstraint *redTop = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:30];
    NSLayoutConstraint *redRight = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-30];
    NSLayoutConstraint *redLeft = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
    NSLayoutConstraint *redHeight = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:50];
    
    //添加红色View的约束
    [self.view addConstraints:@[redTop,redRight,redLeft]];
    [redView addConstraint:redHeight];
    
}

@end

五、sizeClass的基本用法

与其说科技的发展,拉近了空间中任意两点的距离,让交流、信息传递更加便捷。倒不如说由于交流和信息传递的需求更加迫切而推动了科技的进步。大屏显然是一典型的例子,屏幕尺寸的相对增大,一定程度上方便了交流和信息传递,反之,相对小的屏幕对信息传递会有一定的局限。所以苹果推出大屏幕的手机也是人 类进步的需要,并不是什么跟风,扯淡结束。

屏幕大了,尺寸多了,带给开发者的自然是适配方面的工作量和思考。正如大家知道的那样,苹果是一家最具追求的公司,他当然会推出可行的解决方案就是sizeClass。

sizeClass:对屏幕尺寸进行了抽象,不在拘泥于具体尺寸。因为尺寸一直都在变化,我们如果按照尺寸去做适配,一定会很累的。

sizeClass针对iOS设备的屏幕进行了抽象分类:

compact (紧凑 - 小)、Any (任意)、Regular (宽松 - 大)。

总结几点:

1.sizeClass只是对屏幕进行了抽象分类,具体做屏幕的适配还得用autoLayout。

2.没有了横竖屏的概念,也没有了具体尺寸,不用在去谈具体的iphone6 plus还是ipad air2。

3.把高度和宽度都抽象为上边的3种,3*3也就是总共9种类型。注意是9种类型,不是9种屏幕尺寸。

这样做的结果就是你可以做好一个interface builder适配,然后不管在iphone还是ipad中都可以用了,这就是苹果的意愿。打开xcode如果新建一个universal项目,在xcode6之前会默认有两个storyboard,一个是iphone的,一个是ipad版本的。xcode6之后只有一个,并且是正方形的,也就是说不管你做那种屏幕尺寸的app(无论是ipad还是iphone),都只用这一个storyboard就可以了。

具体分类如下:

Snip20150829_10

图中9个格子代表 3*3的9中抽象,具体每种代表了那些含义可以选中看看。

比如iphone的竖屏它是这样抽象的:compact width * regular height。

Snip20150829_14

然后适配还是使用autoLayout,我就不再重复演示了,需要注意的是我们需要根据需求选择正确的sizeClass抽象屏。

iOS8加了sizeclass后,控件也多了个属性,在storyboard上托个label出来(以label为例),选中,在右边的菜单区域可以看 到:installed。这个是用来控制改控件什么情况下显示,当前什么都没约束,表示Any * Any,就是不管是iphone什么尺寸还是ipad什么尺寸都可以显示,点击左边的小加号+可以用sizeClass控制什么情况显示。同样的还有字体、图片显示。

Snip20150829_15控制什么情况显示什么样的字体。

Snip20150829_17

各种苹果设备的分辨率

Snip20150829_18各种iPhone对应尺寸

Snip20150829_19