技術有限,很少寫文章。在iOS中,我們知道布局一直是頭疼的事,它不像Android提供XML,系統有一個解析器負責解析并布局,雖然故事板文件也是xml文件,由系統解析,但只有鬼才會編輯那個XML。在iOS中,我們通常對frame進行賦值或使用故事板和NSLayoutConstraint, 那個VLF或許也有的用,但太煩了。使用NSLayoutConstraint用代碼寫約束,在開發中我很少用,一般直接通過故事板拉約束就可以了。在此我們不討論特別復雜的布局,因為如果是特別復雜的布局,我們可以考慮結合h5來做,使用JS做交互上的東西。那是另外一套體系了,現在我們考慮使用效率更高的原生控件的布局方式。
在布局中,網上見到使用代碼也可以方便布局的第三方庫:Masonry, 后來又看到國內牛人寫了SDAutoLayout,在實際開發中,也可能會用到使用代碼布局的情況,因為SDAutoLayout的API相當精美,而且有大量的Demo可以參考,比如這里,于是我簡單看了一下SDAutoLayout的源代碼,并做一個小分析。
SDAutoLayout提供的API漂亮:
比如我要一個視圖:
上:20
下:20
寬:100
高:100
UIView *view = UIView.new;
view.sd_layout.leftSpaceToView(self.view, 20).bottomSpaceToView(self.view, 20).widthIs(100).heightIs(100)
基本原理分析:
view是沒有sd_layout
屬性的,作者使用動態關聯,擴展UIView
, 調用getter
方法時sd_layout
對象生成,此對象作為view對象的布局控制模塊, 每次生成一個sd_layout對象,都把它加為super view
的autolayoutModelsArray
中,sd_layout
對象即SDAutoLyaout
的實例,SDAutoLayout
下設left, top, bottom, right
等對象負責具體的布局... 用文字解釋不了,看個圖:
可見作者的面向對象思想是相當牛B的。
當我們調用 view.sd_layout.leftSpaceToView(self.view, 20)
時,sd_layout
對象的leftSpaceToView
是個懶加載的block
, 我們這樣是調用這個block, 這樣在內部SDAutoLayout
對frame進行賦值,所以,SDAutoLayout
并不像其名字AutoLayout所示通過NSLayoutConstraint
來對view進行布局的,它是本質上通過修改視圖的frame
來處理的。
繼續:當調用[someView updateLayout]
的時候,我們順著API找下去:
// UIView的Category中:
- (void)updateLayout
{
[self.superview layoutSubviews];
}
也就是說,當對一個view updateLayout的時候,它會調其父視圖的layoutSubviews, 而當調用layoutSubviews時,SDAutoLayout使用方法交換,調用sd_layoutSubviews,我們可以看到交換方法的源代碼:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *selStringsArray = @[@"setText:"];
[selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) {
NSString *mySelString = [@"sd_" stringByAppendingString:selString];
Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString));
Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString));
method_exchangeImplementations(originalMethod, myMethod);
}];
});
}
調用sd_layoutSubviews
// UIView的Category中:
- (void)sd_layoutSubviews
{
[self sd_layoutSubviews]; // 此處由于方法交換,調用[self layoutSubviews]
[self sd_layoutSubviewsHandle];
}
在sd_layoutSubviewsHandle
中,我們知道,看上面圖中視圖有一個autolayoutModelsArray
,它記錄著其所有子視圖用SDAutoLayout
布局的對象,并可以找到每一個子視圖所關聯著的SDAutoLayout
對象,所它來布局。上面談到,在SDAutoLayout
中有left, top, bottom, right
等具體的對象,在這些對象中都有一個refView代表其所參照的視圖,我們可以把它想像成鏈表,鏈表的上一個節點動了,其被參照的視圖根據refView
找到前一個節點,從而作刷新。
但是,想來想去,由于SDAutoLayout
是基于對原始布局方式frame
進行設置,這就可能隱藏一個弱點:如果被依賴的視圖沒有準備好,這時候someView需要刷新會不會亂?我們寫個程序測一下:
我們做兩個視圖:
View0 - 紅色
View1 - 綠色
代碼如下:
- (void)test {
UIView *superView = self.view;
UIView *view0 = UIView.new;
view0.backgroundColor = [UIColor redColor];
[self.view addSubview:view0];
UIView *view1 = UIView.new;
view1.backgroundColor = [UIColor greenColor];
[self.view addSubview:view1];
// view0參照view1
// 先寫view1的布局,再寫view0的布局
view1.sd_layout.rightSpaceToView(superView, 20).topSpaceToView(superView, 100).widthIs(100).heightIs(100);
view0.sd_layout.leftSpaceToView(superView, 20).topSpaceToView(superView, 100).heightIs(100).rightSpaceToView(view1, 100);
}
程序運行正常,我們得到了期望的結果,但是如果把view0的布局和view1的布局調換一下:
- (void)test {
...
// view0參照view1
// 先寫view0的布局,再寫view1的布局
view0.sd_layout.leftSpaceToView(superView, 20).topSpaceToView(superView, 100).heightIs(100).rightSpaceToView(view1, 100);
view1.sd_layout.rightSpaceToView(superView, 20).topSpaceToView(superView, 100).widthIs(100).heightIs(100);
}
運行錯亂,紅色視圖不見了。
這就是剛才所說的,位置調換,沒有得到及時刷新的原因,這無疑是SDAutoLayout不好的地方,如果我們非要先寫view1的布局,再寫view0的布局,就要手動加上刷新:
- (void)test {
...
// view0參照view1
// 先寫view1的布局,再寫view0的布局
view0.sd_layout.leftSpaceToView(superView, 20).topSpaceToView(superView, 100).heightIs(100).rightSpaceToView(view1, 100);
view1.sd_layout.rightSpaceToView(superView, 20).topSpaceToView(superView, 100).widthIs(100).heightIs(100);
[view1 updateLayout]; // 加上這一句
}
為什么加上這一句就好了呢?參照上面我們的圖1中,當調用 [view1 updateLayout];
的時候,實系上會調用其superView的layoutSubviews,而superView維護了一個autolayoutModelsArray,這個數組存儲了子視圖的布局模塊SDAutoLayout對象,然后再次刷新,這時候view1已準備就緒,當然就好了。
但是在Masonry中,我們調換位置寫都能得到正確的結果:
[view0 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.offset(20);
make.top.offset(100);
make.height.equalTo(@100);
make.right.equalTo(view1.mas_left).with.offset(-100);
}];
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.offset(100);
make.right.offset(-20);
make.width.equalTo(@100);
make.height.equalTo(@100);
}];
Masonry的工作原理待后續,但它是基于NSLayoutConstraint的,NSLayoutConstraint說到最最底部就是一個數學公式,我們關心不了那么多,Masonry和SDAutoLayout最大的不同就是上面所說一個基于對frame的設置,一個是基于NSLayoutConstraint.