转载

iOS适配之旅——Autolayout时代

开篇

其实对于 Autolayout 的资料非常非常的多,博主也是略自皮毛,也上不了大雅之堂。这里只是给大家稍微整理一下,可定也不是很全面,主要是针对 AutoLayout 给这个系列进行稍微的讲解一下。其实呢,对于很多人来说 Autolayout 都懂的非常多,我呢,其实也不是很想写,我也是被人架着刀放在脖子上去写的。既然大家不想听,那这篇就到此为止吧。(特么的,你是来骗我们进来的么,要么将要么退票钱:rage:)好吧,既然大家都想稍微听听,那就听老娘,咳咳咳,不好意思,听本少爷慢慢说来。

引言

其实相对于 Autoresizing 来说, Autolayout 是另一个质的飞跃。他能适配的情况更多。而对于 Autolayout 来说他是一个非常强大的工具,即使对于之后的 Size Callses 来说其中其中用的还是 Autolayout ,那么久有人会问了, AutoLayout 到底是什么呢,或者说是他是怎么实现的呢。其实 Autolayout 都是基于一种叫做约束( NSLayoutConstraint )的东西。其实很多iOS程序员应该都懂。至于是关于 NSLayoutConstraints 入门我就不说了。这里我就开始说一些我想说的东西吧。 好了接下来我主要分三个方面来给大家讲述如何添加约束。当然,下面的知识最好是建立在对 NSLayoutConstraint 有一定了解的情况下来看,毕竟我是一个非常傲娇的啦。

代码添加

首先其实对于 Autolayout 来说添加的方式有两种,分别是xib和代码添加。 而对于xib上的添加小伙伴应该会比较熟悉一点,那么我们就从代码添加开始吧。众所周知,所谓的 Autolayout 时间里在一些列的 NSLayoutConstraint 的实例上进行确定每个控件的大小以及位置的。

创建

好了下面先说说 NSLayoutConstraint 在代码上是如何创建的吧。

iOS 6

上代码,关门放OC:

// 水平居中 [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; // 垂直居中 [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]]; // 宽度为20 [centerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:20]]; // 高度为20 [centerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:centerView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]]; 

Swift也就类似了,这里就写一个吧。Swift上:

NSLayoutConstraint(item: firstItem, attribute: .Trailing, relatedBy: .Equal, toItem: secondItem, attribute: .Leading, multiplier: 1.0, constant: 0)   

如果想要用代码进行添加约束,记得将对应的 translatesAutoresizingMaskIntoConstraints 属性设置成NO或者false.

可以看得出其实一个 NSLayoutConstraint 是由两个控件以及对应的属性和关系得到的。(当然两个控件也有可能是指同一个控件,如要设置一个控件的高等于宽的情况等)那么,这里就采用网络上经常看到的等式进行说明一下,其实这里就是 firstItem.attribute relatedBy secondItem.attribute * multiplier + constant 这样的等式。当然其实上面方法还有一个参数是没有初始化的,就是 priority ,这个用来规定是用来规定优先级的,数值是从1-1000,数值越大优先级越高。优先级高的先满足,在满足优先级高的情况下再去满足优先级低的。通过上述方法创建的情况下 priority 是默认值是1000的。 当然对于firstItem和secondItem还是有一定条件约束的,也就是这两个视图有同一个“祖先”视图,比如下面这种情况是允许的:

  • UIView:A
    • UIView:B
    • UILabel
    • UIImageView
  • UIView:C
    • UIButton

那么在这种情况下 UILabel和UIImageView 以及 UIViewB和UIImageView 之间是可以建立约束的,但是 UIButton和UIView:A中的其他控件 之间是不可以建立约束的。这是为什么呢,这个就有点像在加家谱中。如A家族中的所有成员都有同一个祖先,那么A家族中的所有成员之间多少都扯的上一些亲属关系,可是你突然来一个B家族,他们之间没有血缘关系,平时也没有关联,那么总不能说B家族中的成员跟A家族中的成员有亲属关系吧。(那B家族中的成员嫁给A家族呢?)咳咳咳,保安保安,这里有人捣乱,赶紧拉出去。

好了,回归正传,这里其实还有一个很多刚开始用代码写约束的人有的一个疑问,就是经常我们需要给某一个 UIView 添加约束,那么它的约束应该添加到firstItem还是secondItem呢?其实单纯的说添加给他们两个哪一个控件都是不够准确的,当然在一些情况下也有可能是两个Item中的一个。其实应该是加在离他们最近的"祖先"上。当然这个"祖先"也有可能就是他们自己。比如 UIILabel和UIView:B 的约束就加在 UIView:B 上。而 UIImageView和UILabel 加在 UIView:A 上。

除了上面这种创建方式以外,还有在采用 VFL 来进行创建一系列的约束,比如他可以为一个视图创建好一个方向上的所有约束之类的。但是关于 VFL 资料还是相对比较少,而且官方文档讲解的也比较少。博主也不爱用这种方式,如果有兴趣的小伙伴可以Google中搜索百度然后再搜索VFL来查找资料。:smiling_imp: 不过这里还是提供一下他的创建方法:

// OC代码,在NSLayoutConstraint中 + (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary<NSString *,id> *)metrics views:(NSDictionary<NSString *, id> *)views; 
// Swift代码 public class func constraintsWithVisualFormat(format: String, options opts: NSLayoutFormatOptions, metrics: [String : AnyObject]?, views: [String : AnyObject]) -> [NSLayoutConstraint]   

iOS 7

关于Auto Layout在iOS7上的变动相对比较少,其中最大的变动应该是关于创建约束的方式进行巨大变化(预期说是iOS 7的变化,不如说是Xcode的变化),在Xcode 5中是支持了通过Nib来进行添加约束了,详情看最后一小节。

iOS 8

在iOS 8中添加了许多 NSLayoutAttribute 的属性,让在添加约束上更加方便以及可添加的约束更加细致到具体细节。这里需要特别提的是关于 Margin 的属性,具体是什么情况可以看看下文的一些提到的一些地方。

iOS 9

是不是觉得上面的创建方式麻烦的要死。博主也觉得,所以在Github上就出现了一个开源叫做 Masonry ,有兴趣的小伙伴可以看看,特别是那些需要从低版本开始兼容的小伙伴多看看。 如果即熟悉 Masonry 并且也熟悉iOS 9关于AutoLayout的新特性的小伙伴一定觉得有一部分特性是类似的。关于建立约束上,iOS 9在 UIView 中添加了 NSLayoutAnchor 以及其中的API方便大家在创建约束上更加直观以及方便。 下面稍微上一下代码,具体的大家可以查看 NSLayoutAnchor 这个类:

// 可以这样 [UIImageView.trailingAnchor constraintEqualToAnchor:UILabel.leadingAnchor] // 或者 [UIImageView.trailingAnchor constraintEqualToAnchor:UILabel.leadingAnchor constant:20] 
// Swift 代码 UIImageView.leadingAnchor.constraintEqualToAnchor(UILabel.trailingAnchor)   // 或者 UIImageView.leadingAnchor.constraintEqualToAnchor(UILabel.trailingAnchor, constant: 20)   

是不是觉得相对于之前创建方式更加直观以及方便,并且这个创建好之后完全不用在意他是哪一个 UIView 来添加 Constraint 。它默认会将其加到正确的视图上。当然在建立好正确的Constraints之后,记得用 NSLayoutConstraint 中的 activateConstraints 来激活对应的约束。

在iOS 9中还添加了一个名为 UILayoutGuide ,这个组件虽然不像是 UIView 一样显示在界面上,但是确实有想其他视图控件占据着布局中的一个位置和空间,就好像有一个有一个透明的 UIView 一样,但是它却不在视图的层次中。下面举一个使用的例子:chestnut::

UILayoutGuide *space1 = [[UILayoutGuide alloc] init];   [self.view addLayoutGuide:space1]; UILayoutGuide *space2 = [[UILayoutGuide alloc] init];   [self.view addLayoutGuide:space2]; [space1.widthAnchor constraintEqualToAnchor:space2.widthAnchor].active = YES; [self.saveButton.trailingAnchor constraintEqualToAnchor:space1.leadingAnchor].active = YES; [self.cancelButton.leadingAnchor constraintEqualToAnchor:space1.trailingAnchor].active = YES; [self.cancelButton.trailingAnchor constraintEqualToAnchor:space2.leadingAnchor].active = YES; [self.clearButton.leadingAnchor constraintEqualToAnchor:space2.trailingAnchor].active = YES; 
let space1 = UILayoutGuide()   self.view .addLayoutGuide(space1)   let space2 = UILayoutGuide()   self.view.addLayoutGuide(space2)   space1.widthAnchor.constraintEqualToAnchor(space2.widthAnchor).active = true   self.saveButton.trailingAnchor.constraintEqualToAnchor(space1.leadingAnchor).active = true   self.cancelButton?.leadingAnchor.constraintEqualToAnchor(space1.trailingAnchor).active = true   self.cancelButton.trailingAnchor.constraintEqualToAnchor(space2.leadingAnchor).active = true   self.clearButton?.leadingAnchor.constraintEqualToAnchor(space2.trailingAnchor).active = true   

上方的代码是建立的三个按钮,并且在 saveButtoncancelButton 以及 clearButton 之间建立了相等的间距。

除了以上的新增内容外,其实在iOS9中也添加了一个很重要的视图,如果那些只从iOS9开始兼容的小伙伴,可以多用这个视图------ UIStackView 。有兴趣的小伙伴可以多去尝试以下,是一个非常不错的空间,能适配一些之前适配过程中适配不好的一些情况。当然如果有小伙伴希望从iOS6.0开始兼容,但是也想用 UIStackView 的功能的话,小伙伴可以使用以下用 FDStackView 这个控件。由于 UIStackView 不是本篇的重点,所以这里就不给大家展开来讲解了。

好了,既然上面说了添加约束,那么在代码上怎么添加约束呢?

添加(删除)约束

iOS 6

直接上代码吧,先来个OC版的:

[UIVIew:A addConstraint:[NSLayoutConstraint constraintWithItem:UIImageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:UILabel attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]] 

然后来个Swift版本

UIView:A.addConstraint(NSLayoutConstraint(item: UIImageView, attribute: .Trailing, relatedBy: .Equal, toItem: UILabel, attribute: .Leading, multiplier: 1.0, constant: 0))   

通过上述代码就可以进行添加了。当然除了这个 addConstraint 还有一个 addConstraints 方法是用来添加多个约束的情况。当然,在添加约束之后,小伙伴们最好调用一些跟新最近的“祖先”的 layoutIfNeeded 方法。这样才能生效。 除了添加约束的Add方法外,苹果官方还提供了对应的remove方法,与之相对应的分别是 removeConstraintremoveConstraints 的方法,小伙伴可以自己去尝试一下。

当然,如果在讲约束的某一个视图删除之后,不要忘了将含有该视图的所有约束情况,否则会出现crash的情况

iOS 8

上面提到添加和删除约束的方式,可是根据前人的经验,发现通过Add和Remove的方法来控制约束的话,相对来说还是比较笨重的,而且效率不是很高。为此,苹果官方在iOS 8中推出了 activateConstraintsdeactivateConstraints 的接口。特别是有些时候我们只是针对两到三种不同情况进行不同的适配,那么在重复的使用Add和Remove的方式会使我们App的效率降低很多,因此在iOS 8中推出了 activateConstraintsdeactivateConstraints 。 那么具体怎么用呢?其实也是非常非常简单的,只要通过下面这种方式即可:

// 使约束生效 [NSLayoutConstraint activateConstraints:constraintsArray] // 使约束失效 [NSLayoutConstraint deactivateConstraints:constraintsArray] 
// 使约束生效 NSLayoutConstraint.activateConstraints(constraintsArray)   // 使约束失效 NSLayoutConstraint.deactivateConstraints(constraintsArray)   

当然相对应的在 NSLayoutConstraint 中也添加了 active 的属性,我们可以通过这个属性来判断一个约束是否生效。 并且在WWDC 2015上也建议尽量用 activateConstraintsdeactivateConstraints 来提高程序的效率。

可是如果小伙伴们在自定义的控件的时候,如果将添加约束的代码放在 updateConstraints 来进行添加的,然后大家加完之后发现是没有效果的,那是因为这里其实是需要对UIView的 requiresConstraintBasedLayout 这个方法进行重写,需要将其返回值设置成 YEStrue 才可以。

Priority

关于Priority在前面我们也稍微提过,就是针对优先级来说的。可是对于 Priority 来说还有两个特别的优先级,分别为 Content Hugging Priority 以及 Content Compression Resistance Priority ,这两个如果有了解的童鞋几乎可以跳过,没了解的童鞋这里稍微讲一下。通过这两个属性来看看优先级是怎么工作的。

在讲解这个之前,先给大家讲一个概念,叫做 Intrinsic size 。什么叫做 Intrinstic Size 呢?这里举个例子,比如对于一个 UILabel 字体、内容等一系列参数,其实是可以立即确定 UILabel 的Size的。那么这种内在的Size,不用通过其他手段或者是跟其他视图无关而能获得的Size被称为 Intrinstic Size 。类似的还有 UIButton 以及 UIImageView 都有对应的 Intrinstic Size 。换一句话来说,其实在我们对于这些控件的Size不进行设置,而只对Position进行设置的情况下,这些控件都会自动根据内容进行产生 Intrinstic Size

好了,继续说上面两个 Priority ,这两个 Priority 可以理解为 可以压缩度 以及 可拉伸度 (当然别的博客可能有别的说法),不过中文的翻译都是为了让童鞋们更容易理解。至于这里我为什么叫做 可压缩度可拉伸度 呢?因为对我来说 Hug 是拥抱的意思,那这个 Content Hugging Priority 应该是如果优先级越高,那就 可压缩度 应该就越高,那剩下的另一个就相反咯。如果 Content Compression Resistance Priority 的值越大,那就是可拉伸度就越高咯。而这里的可拉伸度和可压缩度针对的对象是什么呢?聪明的小伙伴们应该猜出来了,就是 Intrinstic Size

下面用一个:chestnut:来解释给大家看看是什么意思。 比如我有一个昵称的长度不确定,可是当我给 UILabel 添加了一个 Width 的约束为60,可是昵称突然长度变成了70怎么办,其实只需要将 Content Compression Resistance Priority 的值设置的大于60约束的Priority即可。也就是在两个针对同一个属性值约束的情况下,当然是优先满足大的,也就是可拉伸度的属性啦。虽然你说宽度为60,可是我的等级比你大,也就是说先满足我,所以可以拉伸。 同样的,对于如果出现 Width 的约束为60,可是昵称的长度变成50的情况,如果 Content Hugging Priority 的优先级大于 Width 的情况下, UILabel 的宽度也会变成50。 下面通过两张图来看看上面所说的例子: iOS适配之旅——Autolayout时代 iOS适配之旅——Autolayout时代 可以看出虽然宽度都设置为60了,可是在内容多或者少的时候,其宽度都会根据内容还自动压缩或者拉伸。因为我讲 Width 这个约束的 Priority 设置为10,而 Content Compression Resistance PriorityContent Hugging Priority 的值分别为251何750,所以总会根据内容进行变化,当然其实这样就跟没设置宽度约束一样了。因为 UILabel 的宽度总是等于 Intrinstic Size 。 那么在代码上怎么进行设置呢?其实也是非常简单的:

// Objective-C代码 [longLbl setContentCompressionResistancePriority:750 forAxis:UILayoutConstraintAxisHorizontal]; 
// Swift代码 longLbl.setContentHuggingPriority(750, forAxis: .Horizontal)   

不管从代码上还是xib上,我们都可以看得出来,关于这两个约束其实还有分的是水平方向还是垂直方向,其实 压缩拉伸 就是指针对一个方向的嘛。

xib和storyboard的逆袭

这边我们将讲讲在Xcode中一些常用的关于适配方面的工具吧,用好这些工具能让你的适配效率更好,而且也能体会到其中的便利的地方。

工具

首先先来说说在xib和storyboard(以下都用xib代替二者)右下角的这个几个按钮 iOS适配之旅——Autolayout时代 的功能,以及提供讲解以下大概的用途和使用方法。

Stack

左边第一个工具的功能非常简单,就是将选择好的几个控件放到一个 UIStackView 中,由于是 UIStackView 所以这里就不进行展示了。

Align

从图表可以看得出来他是针对于两个控件之间,由下图可以看得出来这个主要是针对两个视图相对位置的功能,洋气一点的说话就叫Align咯。

iOS适配之旅——Autolayout时代

那么具体怎么能方便的用这个功能呢?其实也非常简单的,就是只要将要进行对其的几个控件都选择然后点击要根据要对其的方式,选择对应的方式即可,具体方法如下:

iOS适配之旅——Autolayout时代

可以看得出上图是将三个控件先选中(按住cmd然后左键点击需要选择的控件)后,然后再点击需要对其的方式即可。这里指的一提的在下方有一个 Update Frames 的下拉框里面有有三个选项,分别是: None , Items of New Constraints 以及 All Frames in Containers ,那么这三个都是什么用的呢?其实可以看得出来这个主要是根据控件的位置和大小的,从名字就可以看得出来 None 就是不更新,这里主要讲下后面两个,第一个是对于那些新加的约束有关的控件都会出行刷新布局,而对于那些跟新的约束无关的视图不会变化。而最后一个,只要在同一个Container中的所有控件的视图都会进行刷新布局。小伙伴们可以试试。

Pin

关于右边第二个的功能总结起来就是添加增对其他控件(默认是父控件)以及控件本身的一些约束,怎么说呢?看看点击Pin这个按钮的弹出框吧。

iOS适配之旅——Autolayout时代

上图是只选择一个控件的情况下,可以看得出当前情况下的 Equal WidthsEqual Heights 以及 Align 都是暗的,即不可选择的。在这种情况下默认情况下是针对离自己最近的控件的约束以及自己的约束。 这里主要讲解一下两个部分上面那个部分,其他部分都是很好理解的,即上面的四个选择上面四个选择主要是针对里自己最近的一个控件的约束,怎么说呢?看下这张图:

iOS适配之旅——Autolayout时代

如果在红色控件添加上下左右的约束的情况下,控件下边(Bottom)的约束的相对约束是UILabel的上边。而在除了上下左右以外,还有一个 Constrain to margins 的单选框,而这个主要是针对父控件来说的。那么具体是什么意思呢?其实简单的理解就是针对父控件20单位的位置的约束。也就是说如果我们选择这个选项,并且我们选择了上下左右(假设是针对父控件的情况下)都为0的情况下,那么其实这个时候就等价于我们具体父控件的上下左右都为20的情况。那么这个可以用在什么时候呢?博主对于具体的使用场景也不是很清楚,不过看过一篇文章有讲到一个使用场景,即在iOS 8的情况下,横屏的时候status bar的默认下是隐藏的(只是指不占高度位置,而不是完全不见),即我们的视图是从屏幕最顶端开始布局的,那么这个时候可能就会出现字跟status bar的重合,那么这个时候就有必要设置成针对 Margin 的约束了。

在上面我们提到了 Equal WidthsEqual Heights 以及 Align 三个不可使用的情况下,那么在上面情况下这些是可以用的呢?其实也很简单,就是在你选择多个控件的情况。在多选的情况下,那么这里的约束要么是相对于这两个控件(如 AlignEqual Widths 等),要不然就是两个都是同样的针对选中控件的最近控件的约束,大家可以多去试试。这里就不一一讲述了。

Resolve Auto Layout Issues

终于到最后一个了,从名字来看就知道这个其实是针对当出现问题的时候的解决方案。一般来说针对那些出现 waranings 的处理方案。那么具体有哪些可选的方案呢?就不告诉你,就不告诉你,就不告诉你。

iOS适配之旅——Autolayout时代

可以看的出来,主要分为两大块,一块是所谓的 Selected ViewsAll Views in View ,其实上下两个中的每个选项的功能是一样的,只是针对的视图不同而已。 Selected Views 是针对所选择的视图,而 All Views in View 是指所有的视图。所以这里就针对其中一个进行说明:

  • Update Frames:根据当前约束改变视图布局,快捷键是 alt + cmd + = 这个快捷键很好用
  • Update Constraints:根据当前的Frame来修改约束
  • Add Missing Contraints:对那些还缺失约束视图的添加缺失的约束,当然这里添加的约束是Xcode推荐的
  • Reset to Suggested Constraints:将当前视图的约束变成Xcode推荐的约束(这个反而用的比较少)
  • Clear Constraints:清除所有跟视图相关的约束

关于上方的提到的所谓的Xcode推荐的约束本宝宝真的不知道他是根据什么形式进行推荐的,所以不要再逼本宝宝了。麻烦那个知道的小伙伴能给本宝宝科普一下。 其实除了在这里的四个按钮以外,在菜单栏里面的 Editor 项中也有很多关于xib上的适配。这里就不一一讲解了,小伙伴可以自己去试试,里面有一些功能也是挺好用的,如果 Editor -> Embed In 这个,还有 Editor -> Algin 也是很好用的功能。

Utitlities视图

关于这个这个视图估计小伙伴们熟的不能再熟了,那么他到底是什么鬼呢?

iOS适配之旅——Autolayout时代

够熟悉吧,这里我就主要就讲讲 Utilities -> Show the Size inspector (倒数第二个tab)。毕竟跟约束相关的主要内容都是在这个tab中的。其中可以看到中间有一部分叫做 Constraints 的地方,可以看得出来我们可以在图中看到上下左右,水平垂直中线以及宽和高的约束。当其为虚线的时候代表其在这个地方没有约束,实线表示其有在这个地方有约束。

那么我们可以怎么使用这个地方的 Constraints ,首先我们可以点击某一个约束,如下图中我们点击了Bottom的约束,那么在下方我们可以看到所有跟选中视图的Bottom相关的约束都会列出来,比如和父视图的约束以及跟子视图的约束。

iOS适配之旅——Autolayout时代

这里可以看得出 Constraints 可以看得出某个位置的约束,有一种类似过滤的功能。

其次下方的约束也可以进行点击右边的 Edit 进行修改约束的属性当然也可以通过双击来进行查看到具体约束的信息。当然也是在 Utilities -> Show the Size inspector 这个位置。这里顺带提一句,当点击右边 Edit 的这个效果也可以在xib上双击某个约束出现相同的效果,如下图:(小伙伴可以自己试试) iOS适配之旅——Autolayout时代

然后我们再讲讲约束详情的一些东西吧,看看下图是一个约束的详情:

iOS适配之旅——Autolayout时代

可以看得出 First itemSecond item 是讲上面的 ItemAttribute 合在了一起。并且这里指的注意的是当如果是Button的时候,如果是 MarginAttribute 的时候是相对文字的约束,我们都知道在 UIButton 在周围都会留一下空间,所以我们经常设置 UIbutton 的约束的时候,都会针对整个 UIButton ,如果在这里选择Attribute为 Margin 相关的时候,则会针对里面文字。小伙伴可以试试,剩余的东西大家应该都比较熟悉。

当然这里还有一个需要提的,就是 Identifier 在出现约束报错的情况下回起到非常大的作用。小伙伴们如果在出现报错的情况下,建议大家设置 Identifier 这样能将报错的信息从全地址的状态变化成显示具体 Identifier 的状态。

xib的结尾

最后我们说说Xib最常用的一种添加约束的情况,小伙伴应该都会,就是通过点击按住 ctrl 然后点击鼠标左键进行拖拽进行添加新的约束,这里我就不给大家献丑了。而关于出现Xib上的约束的 warningerror 的情况小伙伴们应该也知道怎么处理了,都是非常常用的方法,这里就不进行累述了。

Skill-干货

好了,终于到了这里了。上面理论的东西太多了,小伙伴们可以稍微消化消化。虽然内容多,可是大部分来说还是偏操作性质的,所以接受起来可能相对容易一点。想到以前上课看那么多理论的东西就头疼,我还是比较喜欢实践。咱们的老马克思主义说了,实践是检验真理的唯一标准嘛。在实践多了之后,对于一些理论的知识的接受度就更深入了。好了,废话不多说了,这里来说一些干货吧,基本上都是针对于Xib的。虽然博主是个IB粉呢。

干货1 ---------- Preview工具

那么什么是Preview工具呢?其实非常简单,同样一幅图可以搞定:

iOS适配之旅——Autolayout时代

看出来了吧,他其实就是一个预览的功能,在大部分普通的适配其实都可以通过 Preview 工具来预览查看,这样就不用每次运行才能看到效果了,通过这个工具能查看在各个尺寸下我们的布局的排列情况。那么这个怎么点出来呢?

其实也非常简单,首先先打开 Assistant Editor iOS适配之旅——Autolayout时代 即中间那个,然后在点最左边那个按钮,就会出现如下的菜单:

iOS适配之旅——Autolayout时代

选择最后一个即可。如果实在不懂的妹子,可以来问我,汉子就算了。哈哈。(Kidding!!!!)

干货2 ---------- 查看视图层级

很经常会出现一种情况,就是我们经常会有视图层级很多,即一个视图嵌套一个视图,嵌套的层很多。特别是如果出现视图重叠的情况下,想要正确的选择到正确视图这里主要讲解三个方法,用三个视图来给大家展示一下:

  • 通过文件导航器来选择

iOS适配之旅——Autolayout时代

  • 通过视图层级来选择

iOS适配之旅——Autolayout时代

  • 通过鼠标和左键选择(其实主要讲的是这个)

iOS适配之旅——Autolayout时代

前面两种都很常见,最后一种怎么出现的呢?很简单,通过安装 cmd + shift 在点击需要选择控件的地方点击左键即可。这样就会列出点击地方的整个视图层次,最上面是在最后面的视图,最下面的是显示在最上方的视图。

干货3 ---------- wrap_content的实现

本人以前也做过Android的开发,在Android的布局中,在设置高度和宽度的时候可以设置成两个值 fill_parent (同match_parent)和 wrap_content 两个常量值。其实两个的意思分别是:子视图根据父视图缩放以及父视图根据子视图内容缩放。简单来说就是根据父视图大小改变还是根据子视图改变。 而在iOS开发中,我们经常用的应该就是根据父视图改变的,从 AutoresizingAutoLayout 相对父控件的约束来说都是根据父视图改变子视图的大小。那么我就在想了,能不能再iOS中实现 wrap_content 即根据子视图的大小来调整父视图的大小呢?其实这个功能的使用场景还是挺多的。 下面我这里就举一个例子来说说,比如我们在一个希望自订一个titleView,这个titleView的左边有一个定位的图标,中间有一个 UILabel 用来显示定位的城市名,右边有一个箭头的图标。那么这个titleView的宽度应该就会根据城市的不同进行缩放了。那么我们在家的时候最外层的 UIView 即titleView就需要根据里面的内容进行所谓的 wrap_content 的处理。

下面我们就用一个Demo在Xib上的约束以及Preview的功能来进行讲解(假设上面所说的titleView中最高的是 UILabel )。首先我们先来看看效果:

iOS适配之旅——Autolayout时代

当前选中的是红色的背景的 UIView 可以从右边的视图看的出来,除了设置了相对父视图的位置信息的以外,没有对 UIView 设置关于大小的约束。那么 UIView 的带下是如何来的呢?可以看得出来,除了针对父视图的位置约束外,剩下的就是子视图相对 UIView 的约束了。那么肯定就是子视图生成的。(废话,你前面不是也说针对子视图内容来缩放父视图大小)其实实现的机理也非常简单,只要保证在距离 UIView 中的子视图在某一个方向上是打通的,例如水平方向上:

  • |UIView.Leading - 20 - UIImageView.Leading|
  • UIImage.Width = 15
  • |UIImageView.Trailing - 20 - UILabel.Leading|
  • UILabel.Width = Intrinsic size .width
  • |UILabel.Trailing - 20 - UIImageView.Leading|
  • UIImageView.Widht = Intrinsic size .width
  • |UIImageView.Trailing - 20 - UIView.Trailing|

可以看到水平上从UIView.Leading到UIView.Trailing是走的通的,而不能出现断掉的情况。而垂直方向就简单了:

  • |UIView.Top - 20 - UILabel.Top|
  • UILabel.Height = Intrinsic size .height
  • |UILabel.Bottom - 20 - UIView.Bottom|

同样在UIView.Top到UIView.Bottom的方向上也是完全走得通的。那么总结来说就是一句话,就是在父控件视图的任何方向上都能保证约束能从一个方向的一段走到另一端,其中不会出现断层的情况下,则能保证其能出现 wrap_content 的效果。就好像是在一个方向上搭建一条路,能从路的一段走到另一端即可。

博主估计这里的原因估计是因为当如果保证某一个方向上的约束链走的通的情况下能让UI布局的内部机制在计算UIView的宽度和高度的情况下都有可参考的值,主要是保证父视图能算出准确的宽和高。

Demo见WrapContentViewController.xib!!!

Tips: 其实下方另外两个干货都和这个干货的原理很类似,小伙伴可以思考一下。由于原理都差不多,所以接下里就稍微一带而过了,大概讲一下实现的方式和原理。

干货4 ---------- UIScrollView的适配

很多小伙伴在刚开始适配 UIScrollView 的情况下都是相当蛋疼啊,因为明明都配置了 UIScrollView 相对父控件的上下左右距离都为0的。也就是 UIScrollView 的大小与父控件一样大,可是为什么偏偏在添加了子视图后,瞬间整个视图的适配就会出错,总是适配不好。记得博主刚开始适配 UIScrollView 是这个表情:

iOS适配之旅——Autolayout时代

那么到底是为什么呢?其实在添加子视图之后出错的原因是适配机制希望我们通过适配来确定 UIScrollViewcontentSize 的大小。为什么这么说呢?其实如果适配成功过的小伙伴都知道,在我们适配好 UIScrollView 之后, contentSize 是不需要设置的,那么如何设置呢?其实跟上面的 wrap_content 一样,保证在每个方向上的"路"都是通的就可以了。

Demo见ScrollViewController.xib!!!

干货5 ---------- 自动计算Cell的高度

还记得之前博主要做一个类似朋友圈(QQ空间、微博等)的功能,即根据文字、图片、评论以及点赞等信息来自动调整一条说说的高度。当初那叫一个纠结啊,也只能说当初博主比较水,只能印着头皮在算所谓的 Frame 那一个一个的 if-else 语句写的博主那叫一个心惊肉跳。深怕其中一个写错了,找问题就麻烦了。结果最后还是因为各种奇葩的原因算的不够准。后来看了2015年的WWDC大会才知道了这个好东西。

由于 UITableViewCell 的宽度都是跟 UITableView 等宽,所以主要是针对 UITableViewCell 的高度。平常我们都是在Xib或者在 UITableViewDelegate 中实现方法来返回,可是其实在iOS 7之后就提供了实现动态高度的方法,具体原理还是跟上面一样,保证在垂直方向上保证"路"是能通的。然后针对tableView设置如下两个属性即可:

tableview.estimatedRowHeight = 60; // 设置UITableViewCell每行大概多高   tableview.rowHeight = UITableViewAutomaticDimension;   
tableview.estimatedRowHeight = 60 // 设置UITableViewCell每行大概多高   tableview.rowHeight = UITableViewAutomaticDimension   

其中第一个属性estimatedRowHeight是用来估算 UITableViewcontentSize 用的。第二个是告诉UITableView去根据一些方法来计算Cell的高度。只要保证这两句代码和垂直"路"是通的情况下,则能实现Cell的动态高度。

苦逼的总结

这篇文章的跨度好久,从三月初就开始写,写到四月中旬,主要是其中的三月份公司的项目太忙了,不仅博客停了,包括开源项目的更新基本也停了,不过这里也慢慢开始恢复分享一些内容给大家。当然其实本来还是比较喜欢讲解一些操作性质的内容,除了这些内容其实还有一些其他的内容,如以前的 Autoresizing 在与 Autolayout 混用的情况下会将之前的 Autoresizing 的配置都改成一个个 Constraints -------- NSAutoresizingMaskLayoutConstraint 的形式,从而让 Autolayout 也能兼容到 Autoresizing

当然关于适配的内容也非常非常的多,我这里也一时半刻说不完,并且苹果官方的适配机制也会在不断的更新,我们需要不断的学习来做到更好的适配。当然这篇文章的中的一些内容与之前整理的WWDC的内容有些重合的地方,在WWDC的内容上也整理了很多关于IB以及AutoLayout的技巧,有兴趣的小伙伴可以返回重新看看。

博主整理文章真的不容易,如果觉得文章内有问题的小伙伴可以留言,我会在看到的时候给你回复。博主整理文章不容易,且读且珍惜。

哦,忘记了,还有关于适配的最后一个部分 Size Class ,小伙伴们记得回来看啊,要有始有终啊。

广告

接下来是广告时间,本人开了一个订阅号,有兴趣的小伙伴关注一下:iOS周记。

可以查找关键字:Weekly_iOS进行关注,也可以扫一扫下方的二维码 iOS适配之旅——Autolayout时代

原文  http://www.pluto-y.com/ios-layout-guides-autolayout/
正文到此结束
Loading...