ios SDAutolayout的使用心得

来源:互联网 发布:木村拓哉 知乎 编辑:程序博客网 时间:2024/06/07 04:35

一、你必须了解以下几个知识点 
1、UIView框架下的 - (void)layoutSubviews 方法 
首先,看下官网文档的解释:

// override point. called by layoutIfNeeded automatically. As of iOS 6.0, when constraints-based layout is used the base implementation applies the constraints-based layout, otherwise it does nothing.
  • 1

大概意思是: 在ios6.0中,当基于约束的布局使用基于约束的布局时,基本实现应用于约束,否则它什么也不做。 
layoutSubviews以下几种情况会被调用: 
1) 直接 layoutSubviews 
2) addSubview 的时候(准确来说,应该是,addsubview所在当前方法走完) 
3)init初始化不会触发layoutSubviews, 但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发 
4)当view的frame发生改变的时候,当然前提是frame的值设置前后发生了变化 
5)滑动UIScrollView的时候 
6)旋转Screen会触发父UIView上的layoutSubviews事件 
7)改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

二、NSObjeect 框架下的 + (void)load 方法 
load方法要点如下
1、对于一个类而言,没有load方法实现就不会调用,但是如果你实现了load 方法,那么当类被加载时它会自动被调用。 
2、在不考虑开发者主动使用的情况下,系统最多会调用一次 
3、如果父类和子类都被调用,父类的调用一定在子类之前 
一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前 
4、Category的load也会收到调用,但顺序上在主类的load调用之后 
5、在load方法,一般用于运行时method_exchangeImplementations 方法交换

三、runtime之Swizzling 
简单来说,交换两个方法的实现。我之前的博客有介绍到交换两个普通的方法实现,请移步: http://blog.csdn.net/qq_18505715/article/details/51174318 
下面,我们来说下,如何自定义方法和 系统方法的交换,我们定义如下:

#import "ViewController.h"#import <objc/runtime.h>@implementation ViewController+ (void)load{    // 定义 swizzlingViewDidLoad方法和 viewDidLoad 交换    Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));    Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));    if (!class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {        method_exchangeImplementations(fromMethod, toMethod);    }}- (void)swizzlingViewDidLoad{   [self swizzlingViewDidLoad];   self.view.backgroundColor = [UIColor redColor];    }- (void)viewDidLoad{    [super viewDidLoad];}@end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

有人看到上面的代码就会疑问了 ? 
楼主,你太粗心了,你在swizzlingViewDidLoad方法中又调用了[self swizzlingViewDidLoad];,这难道不会产生递归调用吗
答案是: 肯定不会。向swizzlingViewDidLoad方法发送消息时执行的却是viewDidLoad方法,向viewDidLoad方法发送消息时执行的是swizzlingViewDidLoad方法。我们上面的代码,系统调用UIViewController的viewDidLoad方法时,实际上执行的是我们实现的swizzlingViewDidLoad方法。而我们在swizzlingViewDidLoad方法内部调用[self swizzlingViewDidLoad];时,执行的是UIViewController的viewDidLoad方法。 
注意: Swizzling 一个方法但不去调用其原始实现,可能造成私有状态的底层假设被打破,影响程序的其它部分。

三、SDAutoLayout介绍 
1、关于SDAutoLayout原理介绍,作者本身的博客 
http://www.cocoachina.com/ios/20160405/15854.html 
github地址: https://github.com/gsdios/SDAutoLayout 
2、下面细谈下我的理解 
1) 对UIViewe而言

创建子视图并添加设置约束 
这里写图片描述

在_subView.sd_layout方法里面是这么实现的,_view.sdlayout 这个方法返回SDAutoLayoutModel,具体实现也是 利用runtime合成get和set方法,用get方法判断是否存在,如果不存在,直接创建,并将需要布局的视图赋值给 SDAutoLayoutModel的needsAutoResizeView 属性,并给set方法赋值。返回model。利用 self.superview.autoLayoutModelsArray 将其添加在父视图的一个数组里面 (备注: 在SDAutoLayout中,每个view都有一个autoLayoutModelsArray数组来管理子view的约束,子view在 调用sd_layout方法时候会初始化一个约束模型并添加到其父view的autoLayoutModelsArray数组中,这就是为什么在使用 SDAutoLayout过程中要先将子view添加到父view然后再做布局设置的原因了,交给父视图,统一由父视图返回总高度) 
这里写图片描述

SDAutolayout 在这边利用Swizzling 做了方法实现替换 
这里写图片描述

其次,在替换方法里面向原声的layoutSubviews发送了消息 
这里写图片描述

重点在- (void)sd_layoutSubviewsHandle 方法里面,我们跳着看下代码。 
这里写图片描述 
首先,在此过程中,我们只看到_subview.sd_layout方法是,创建了autoLayoutModelsArray数组,这个数组里面存的是需要布局的子视图集合。下面遍历了这个数组, 数组caches并为被赋值,所以执行 [self sd_resizeWithModel:model]; 这个方法。

再回过头来看我们设置的约束 
这里写图片描述

注意 :_subView.sd_layout.leftSpaceToView(self,5) 中leftSpaceToSpaceView是个block,返回值是SDAutoLayoutModel * 类型 
这里写图片描述 
这里写图片描述 
紧接着我们继续回过头看- (void)sd_resizeWithModel:(SDAutoLayoutModel *)model 这个方法的实现。我们主要看 leftSpaceToView 这个 
这里写图片描述 
继续点击layoutLeftWithView : model : 方法 , 
这里写图片描述 
我们可以看到,model.left 为true,那么model.left 在哪里赋值呢?我在上面写了一串屏蔽的伪代码 。

        /*         *  weakSelf.left    = item         *  weakSelf.right   = item         *  weakSelf.top     = item         *  weakSelf.bottom  = item         **/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

继续看,view.superview 为__subView的父视图,这个很好理解,那么model.left.refView 呢? 继续看这个 
这里写图片描述 
这里写图片描述 
这里写图片描述 
我们把3张图合在一起看。 我们发现在_subView.sd_layout.leftSpaceToView(self,5) 
这个是个block,传进来的参数是self,相当于第三章图的id viewOrViewsArray 参数 
所以,view.superview == model.left.refView 这个条件是成立的。 
这里写图片描述 
下面继续执行 view.left_sd = model.left.refView.right_sd + [model.left.value floatValue]; 
这行代码才是真正起到布局作用,我们可以看下 left_sd 这个是怎么实现的,很简单. 
这里写图片描述

right_sd的实现思路 
这里写图片描述 
其他的差不多一样的思路。其实,也就是说,SDAutoLayout 的本质根据 frame。

写到这里,我很好奇作者是怎么设置这些链式点语法的, 属于链式编程思想。 这里先吹一波链式编程思想: 链式编程思想是一种能让开发者高潮的编程思想,特点在于所有方法的调用,均可通过一连串的方法调用来实现。可以说只要你的显示器够宽,你就可以在一行内完成对一个控件的所有约束。 
这种编程思想的好处不言而喻,代码整洁、增强可读性、减少重复代码。

设计思路也比较简单: 实现链式编程的关键点在于每个调用的方法的返回值都是一个block,block的返回值是这个对象的本身。这样才能保证每次调用完一个方法之后,仍然可以用点语法来继续调用方法

2、如何让TableView的cell自动计算高度 
先不看作者的思路,一般你的思路是怎么样的呢? 取一个中间变量 UIView *bottomView。可能子视图需要隐藏或者不隐藏。bottom不断的更换指向。当需要高度的时候,返回这个bottomView.bottom (+gap) 或者 bottomView.frame.origin.y+bottomView.frame.size.height (+gap) 
其实,作者的思路跟这个也差不多,只不过简单巧妙点。具体的看下。作者在github是这么介绍的 
这里写图片描述

具体代码 以DemoVC5 为例。作者将其写在 数据源最后面。至于为什么,等会会说到。 
这里写图片描述 
继续看第二步,这一步完成后,就完成了cell高度自适应了 
这里写图片描述

我们都知道UITableviewCell 直接继承UIView。所以,在自定义cell里面创建和添加子视图,和上面的步骤一致。 
当我们为cell 赋值的时候,一般在cell里面重写了model方法。根据上面的步骤,需要添加cell高度自适应的方法

/** 设置Cell的高度自适应,也可用于设置普通view内容自适应(应用于当你不确定哪个view在自动布局之后会排布在最下方最为bottomView的时候可以调用次方法将所有可能在最下方的view都传过去) */- (void)setupAutoHeightWithBottomViewsArray:(NSArray *)bottomViewsArray bottomMargin:(CGFloat)bottomMargin;
  • 1
  • 2

这个方法的实现也很简单,如下.利用数组 添加可能在最下方,还有中间变量记住 间隔 
这里写图片描述

看下sd如何返回cell的高度的 
这里写图片描述 
这个时候,我们会好奇,autoHeight 这个怎么来的? 获取时机很巧妙。 
当我们对一张表 reloadData 的时候。会走数据源方法和代理方法。 但是,一个很重要的顺序一定要明白,先创建cell再返回高度。 走cell方法的时候,会走cell里面的layoutSubViews方法,我贴张图就明白了。 
这边仍需注意一个细节: 在cell里面,子视图所属父视图是cell.contentView 而不是cell ! 前面有讲到,由父视图统一管理。 
这里写图片描述

前面还遗留一个小问题 
- (void)setupAutoHeightWithBottomViewsArray:(NSArray *)bottomViewsArray bottomMargin:(CGFloat)bottomMargin; 为什么这句话要写在 setmodel方法最下面 ? 写到setmodel里面的好处这边应该不用解释了吧。

转载:http://blog.csdn.net/qq_18505715/article/details/71159869#comments