1. 程式人生 > >關於Autolayout和Masonry自動佈局的幾個坑

關於Autolayout和Masonry自動佈局的幾個坑

自動佈局 02 Mar 2016Comments 前言 最近遇到一個複雜檢視:根控制器裡面有上下兩個子控制器,子控制器中各自實現類似PageView的檢視,然後PageView的每一頁是一個WebView,同時中間有個可拖拽的控制元件,實現上下兩個控制器檢視的大小調整。採用子控制器的原因是因為防止所有的邏輯程式碼都混在根控制器中,所以沒有使用nicklockwoodiCarouselSwipeView ,而是採用了之前一直在用的SCPageViewController 。 記錄下自動佈局中遇到的幾個坑。 關於translatesAutoresizingMaskIntoConstraints

因為檢視太過複雜,所以遇到好幾次忘記設定translatesAutoresizingMaskIntoConstraints 為NO的情況。translatesAutoresizingMaskIntoConstraints 預設為YES,也就是按照預設的autoresizingMask 進行計算;設定為NO之後,則可以使用更靈活的Autolayout(或者Masonry)之類的工具進行自動佈局。 關於Autolayout的除錯 剛開始使用Autolayout遇到下面的警告人容易讓人氣餒。經常不知所措而放棄了使用Autolayout。

Unable to simultaneously satisfy constraints.Probably at least one of the constraints in the following list is one you don't want.Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(...........)
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

正如輸出中所述,Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger ,現在介紹下使用UIViewAlertForUnsatisfiableConstraints 的除錯方法。

在UIViewAlertForUnsatisfiableConstraints 新增symbolic breakpoint : 1.開啟斷點導航(cmd+7 )2.點選左下角的+ 按鈕3.選擇Add Symbolic Breakpoint 4.在Symbol 新增UIViewAlertForUnsatisfiableConstraints

新增symbolic breakpoint

再次除錯的時候就可以通過LLDB來除錯了,然並卵,如果你不知道LLDB的話。 所以交給你一個小技巧,新增po [[UIWindow keyWindow] _autolayoutTrace] (OC專案)或expr -l objc++ -O -- [[UIWindow keyWindow] _autolayoutTrace] (Swift專案)。

這樣就可以直接看到輸出: (lldb) po [[UIWindow keyWindow] _autolayoutTrace]UIWindow:0x7f9481c93360| •UIView:0x7f9481c9d680| | *UIView:0x7f9481c9d990- AMBIGUOUS LAYOUT for UIView:0x7f9481c9d990.minX{id: 13}, UIView:0x7f9481c9d990.minY{id: 16}| | *_UILayoutGuide:0x7f9481c9e160- AMBIGUOUS LAYOUT for _UILayoutGuide:0x7f9481c9e160.minY{id: 17}| | *_UILayoutGuide:0x7f9481c9ebb0- AMBIGUOUS LAYOUT for _UILayoutGuide:0x7f9481c9ebb0.minY{id: 27}

其中AMBIGUOUS 相關的檢視就是約束有問題的。0x7f9481c9d990 就是有問題檢視的首地址。 當然進一步的除錯需要LLDB的命令。比如 列印檢視物件: (lldb) po 0x7f9481c9d990<UIView: 0x7f9481c9d990; frame = (0 0; 768 359); autoresize = RM+BM; layer = <CALayer: 0x7fc82d338960>>

改變顏色: (lldb) expr ((UIView *)0x174197010).backgroundColor = [UIColor redColor](UICachedDeviceRGBColor *) $4 = 0x0000000174469cc0

剩下的就是去程式碼中找到這個檢視,然後修改其約束了。參考:Debugging iOS AutoLayout IssuesAutolayout Breakpoints 關於Masonry的使用 必須明確AutoLayout關於更新的幾個方法的區別 setNeedsLayout:告知頁面需要更新,但是不會立刻開始更新。執行後會立刻呼叫layoutSubviews。

layoutIfNeeded:告知頁面佈局立刻更新。所以一般都會和setNeedsLayout一起使用。如果希望立刻生成新的frame需要呼叫此方法,利用這點一般佈局動畫可以在更新佈局後直接使用這個方法讓動畫生效。

layoutSubviews:系統重寫佈局

setNeedsUpdateConstraints:告知需要更新約束,但是不會立刻開始

updateConstraintsIfNeeded:告知立刻更新約束

updateConstraints:系統更新約束

基本使用 mas_makeConstraints:新增約束

mas_updateConstraints:更新約束、亦可新增新約束

mas_remakeConstraints:重置之前的約束

注意 先新增子檢視,才能對子試圖新增約束

如果想使用動畫效果,需要如下程式碼:

//重寫updateViewConstraints方法,進行約束的更新- (void)updateViewConstraints { [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) { make.center.mas_equalTo(self.view); // 初始寬、高為100,優先順序最低 make.width.height.mas_equalTo(100 * self.scacle).priorityLow(); // 最大放大到整個view make.width.height.lessThanOrEqualTo(self.view); }]; [super updateViewConstraints];}// 通知需要更新約束,但是不立即執行[self setNeedsUpdateConstraints];// 立即更新約束,以執行動態變換// update constraints now so we can animate the change[self updateConstraintsIfNeeded];// 執行動畫效果, 設定動畫時間[UIView animateWithDuration:0.2 animations:^{ [self layoutIfNeeded];}];

經過測試,又找到一個方法,remake約束之後直接使用動畫layoutIfNeeded 即可。

self.button = ({ UIButton *button = [[UIButton alloc] init]; button.backgroundColor = [UIColor orangeColor]; [self.view addSubview:button]; [button mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.view); make.width.height.equalTo(@100); make.top.equalTo(self.blueView.mas_bottom).with.offset(20); }]; @weakify(self); [[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { @strongify(self); [self.blueView mas_remakeConstraints:^(MASConstraintMaker *make) { //這裡進行大小狀態的判斷 if (!self.isBigger) { make.top.bottom.left.right.equalTo(self.view).with.insets(UIEdgeInsetsMake(50, 50, 200, 50)); } else { make.center.equalTo(self.view); make.width.height.equalTo(@200); } }]; [UIView animateWithDuration:0.25f animations:^{ [self.view layoutIfNeeded]; }]; self.isBigger = !self.isBigger; }]; button;});

關於UIScrollView的自動佈局 上面提到的頁面遇到了多重的UIScrollView,使用自動佈局的時候也是夠蛋疼的。具體使用技巧參考Masonry自動佈局詳解九:複雜ScrollView佈局在UIScrollView中使用Autolayout佈局以及iOS_autoLayout_Masonry。主要注意點為: UIScrollView自身的約束按照正常的檢視新增。 內部子控制元件的約束不能按照UIScrollView來設定,同時必須完整,否則撐不起contentSize。 考慮到以上兩點,跟計算出來沒什麼兩樣了。

可以使用輔助的contentView來設定,思路大概如下 //首先設定scrollview的約束[_scrollView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); // self.view一樣大小}];//然後設定contentView的約束_contentView.backgroundColor = [UIColor greenColor]; [_contentView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_scrollView); // 大小 = _scrollView make.width.equalTo(_scrollView); // width = _scrollView}]; UIView *lastView;CGFloat height = 25;//新增子檢視,並且設定子試圖的約束,注意top的約束由上一個子檢視決定for (int i = 0; i < 10; i++) { UIView *view = [[UIView alloc]init]; view.backgroundColor = [self randomColor]; [_contentView addSubview:view]; [view mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(lastView ? lastView.mas_bottom : @0); // 第一個View top = 0; make.left.equalTo(@0); // left 0 make.width.equalTo(_contentView); // width = _contentView; make.height.equalTo(@(height)); // height = height }]; height += 25; lastView = view;}[_contentView mas_makeConstraints:^(MASConstraintMaker *make) { make.bottom.equalTo(lastView); // bottom = lastView}];

不過對於我的專案來講計算的太蛋疼了,於是偷了個懶,因為從pageview往裡的每個view都是撐滿父檢視的,所以也就可以使用預設的autoresizingMask進行自適應佈局啦。 SizeClass示意圖 一般如果涉及到iPad的佈局,最好還是用SizeClass比較方便。約束添加註解:

約束新增

約束新增

SizeClass註解:

SizeClass