天天看點

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

目錄

  • 1.Incrementally Adopting Auto Layout
  • 2.Design and Runtime Constraints
  • 3.NSGridView
  • 4.Layout Feedback Loop Debugging

一.Incrementally Adopting Auto Layout

Incrementally Adopting Auto Layout是什麼意思呢?在我們IB裡面布局我們的View的時候,我們并不需要一次性就添加好所有的constraints。我們可以一步步的增加constraints,簡化我們的步驟,而且能讓我們的設定起來更加靈活。

再談新特性之前,先介紹一下這個特性對應的背景來源。

有這樣一種場景,試想,我們把一個view放在父view上,這個時候并沒有設定constraints,當我們運作完程式,就會出現下圖的樣子。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

看上去一切都還正常。但是一旦當我們把裝置旋轉90°以後,就會出現下圖的樣子。

這個時候可以發現,這個View的長,寬,以及top和left的邊距都沒有發生變化。這時我們并沒有設定constraints,這是怎麼做到的呢?

在程式的編譯期,Auto Layout的引擎會自動隐式的給View加上一些constraints限制,以保證View的大小不會發生變化。這個例子中,View被加上了top,left,width,height這4個限制。

如果我們需要更加動态的resize的行為,就需要我們在IB裡面自定義限制了。現在問題就來了,有沒有更好的方式來做這件事情?最好是能有一種不用限制的方法,也能達到簡單的resize的效果。

現在這個問題有了解決辦法。在Xcode8中,我們可以給View指定autoresizing masks,而不用去設定constraints。這就意味着我們可以不用限制,我們也能做到簡單的resize的效果。

在Autolayout時代之前,可能會有人認出這種UI方式。這是一種Springs & Struts的UI。我們可以設定邊緣限制(注:這裡的限制并不是指的是Autolayout裡面的constraints,是autoresizing masks裡面的規則),無論View的長寬如何變化,這些View都會跟随着設定了限制的view一起變化。

上述的例子中,Xcode 8 中在沒有加如何constraint就可以做到旋轉螢幕之後,View的邊距并沒有發生變化。這是怎麼做到的呢?事實上,Xcode 8的做法是先取出autoresizing masks,然後把它轉換成對應的constraints,這個轉換的時機發生在Runtime期間。生成對應的constraints是發生在運作時,而不是編譯時的原因是可以給我們開發者更加便利的方式為View添加更加細緻的限制。

在View上,我們可以設定translatesAutoresizingMaskIntoConstraints屬性。

translatesAutoresizingMaskIntoConstraints == true複制代碼           

複制

假設如果View已經在Interface Builder裡面加過constraints,“Show the Size inspector”面闆依舊會和以前一樣。點選View,檢視給它加的所有的constraints,這個時候Autoresizing masks就被忽略了,而且translatesAutoresizingMask的屬性也會變成false。如下圖,我們這個時候在“Show the Size inspector”面闆上面就已經看不到AutoresizingMask的設定面闆了。

上圖就是在Autolayout時代之前,我們一直使用的是autoresizing masks,但是Autolayout時代來臨之後,一旦勾選上了這個Autolayout,之前的AutoresizingMask也就失效了。

回到我們最原始的問題上來,Xcode 8 現在針對View可以支援增量的适用Autolayout。這就意味着我們可以從AutoresizingMask開始,先做簡單的resize的工作,然後如果有更加複雜的需求,我們再加上适當的限制constraints來進行适配。簡而概之,Xcode 8 Autolayout ≈ AutoresizingMask + Autolayout 。

接下來用一個demo的例子來說明一下Xcode 8 Autolayout新特性。

在說例子之前我們先來說一下Xcode 8在storyboard上新增了哪些功能。如下圖,我們可以看到,在最下方新增加了一欄,可以切換不同的螢幕大小,可以看出,iPhone現在已經分化成6種螢幕大小需要我們适配了,從大到小,依次是:iPad pro 12.9, iPad 9.7 , iPhone 6s Plus/iPhone 6 Plus , iPhone 6s/iPhone 6, iPhone SE/iPhone5s/iPhone5, iPhone4s/iPhone4。下面還可以選擇橫豎屏,和不用螢幕百分比的适應性。

回到例子,我們現在對頁面上這些view來做簡單的AutoresizingMask。右邊的那個預覽界面是可以看到我們加上這些Mask之後的效果。

先是粉色的父View,我們給它加上如下的AutoresizingMask。

給"雨天"的imageView加上如下AutoresizingMask

給"陰天"的imageView加上如下的AutoresizingMask

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

最後給我們的中間的Label加上AutoresizingMask

這個時候我們旋轉一下螢幕,一切正常,View的排版都如我們所願。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

這個時候我們再選擇一下,3:2分屏,這個時候就出現了不對的情況了。Label的Width被擠壓了。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

原因是因為Autoresizing masks并不會向Autolayout一樣,會考慮View的content,是以這裡被擠壓了。

想fix這個Label,我們可以很容易的添加一個constraints來修複。不過這裡我們來談談另外一種做法。

進入到Attributes Inspector面闆,找到Autoshrink屬性,把“fixed font size”切換成“minimum font size”

這個時候就fix上述的問題了。

此時就算是回到landscape,分屏的情況下,已經可以顯示正常。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

接着我們再來處理一下中間的溫度的Label。這個時候我們有比較複雜的需求。這個時候我們就需要用到constraint了。

這個時候我們按時control鍵,然後拖到父View上,釋放,會彈出菜單。我們再按住shift,這樣我們可以一次性選擇多個constraints。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

我們一次性選擇“Center Horizontally in Container” 和 “Center Vertically in Container”。注意這個時候右邊還是AutoresizingMask的面闆,因為這個時候Label還沒有任何的constraint。當我們點選“Add Constraints”的時候,就給Label加上了限制,右邊的面闆也變成了constraints面闆了。

我們再給這個Label繼續加2個constraints。“Horizontal Spacing”和“Baseline”。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

同樣的,從Label拖拽到“太陽”的那個imageView上,再添加“Horizontal Spacing”和“Baseline”限制。

這個時候我們更新一下frame。如下圖所示,選擇“Update Frames”,這個時候所有的frame就都完成了。

這個時候我們更新一下中間溫度的Label的字型大小,這時候計算變大,由于我們的constraints都是正确的,兩邊的View也會随着Label字型變大而變大。

Xocde 8在這個時候就變得更加智能了,會立即自動更新frame。

我們在繼續給晴天的上海加上一個背景圖。添加一個imageView,然後大小鋪滿整個父View,把mode 選擇成“Aspect Fill”

接下面一般的做法就是在這個imageView上面添加constraints,來使這個View和父View大小一樣。但是這種簡單的resize的行為在Xocde 8裡面就不需要再添加Constraint了,這裡我們改用Autoresizing masks來實作。給imageView添加一下這些mask。

我們把imageView放到背景去。這時,我們所有的界面就布局完成了。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

測試一下橫屏的效果

甚至分屏的一樣可以完成任務!

Demo的Github位址,這個demo沒啥難的,就是看看效果。

這就是Xcode 8 的Incrementally Adopting Auto Layout,Autoresizing masks + Auto Layout Constraint 一起協同工作!

二.Design and Runtime Constraints

在我們開發過程中有這樣一種情況,View的constraints會依據你所加載的資料來添加的。是以在app運作之前,我們是無法知道所有的constraints的。

這裡有3種方法可以對應以上的情況。

1.Placeholder Constraints

假設現在我們需要把一張圖檔放在View的垂直和水準的中間,并且距離左邊的邊緣有一個leading margin。并且還需要保持其長寬的比例。而這種圖檔的最終樣子,我們并不知道。隻有到運作時,我們才能知道這樣圖檔的樣子。

為了能在Interface Builder看到我們的圖檔,我們要先預估一下圖檔的長寬比例。假設我們估計為4:3。這時候就給圖檔加上constraints,并且勾上“place order constraint”,這個限制會在build time的時候被移除。

當我們在運作時拿到圖檔之後,這個是時候我們再給它加上适當的限制和長寬比例即可。

2.Intrinsic Content Size

還是類似上面那種場景,我們有時候會自定義一些UIView或者NSView,這些View裡面的content是動态的。Interface Builder并不會運作我們的代碼,是以不到app運作的時候我們并不知道裡面的大小。我們可以給它設定一個内在的content的大小。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性
Setting a design time intrinsic content size only affects a view while editing in Interface Builder.The view will not have this intrinsic content size at runtime.

注意一下上面的說明intrinsic content size僅僅相當于是在布局的時候一個placeholder,在運作時這個size就沒有了。是以如果開發過程中真的需要用到這個内在的content的大小,那麼我們需要overriding的content size

override var intrinsicContentSize: CGSize複制代碼           

複制

3.Turn Off Ambiguity Per View

這個是Xcode 8的一個新特性。當上述2種方法都無法解決我們的需求的時候。這個時候就需要用到這種方法了。Xcode 8給了我們可以在constraints産生歧義的時候,可以動态調整警告級别的能力。

在這個場景中,我們僅僅隻知道我們需要把這個imageView放在水準位置的中央,但是imageView的大小和它的水準位置我們并不知道。如果我們僅僅隻加上了這一個限制的話,Interface Builder就會報紅,因為IB這時候根據我們給的constraints,并不能唯一确定目前的view的位置。

如果我們在之後的運作時,拿到圖檔的完整資訊之後,我們自己知道該如何去加constraints,我們知道該如何去排版保證imageView能唯一确定位置的時候,這時我們可以關掉IB的紅色警告。找到“Ambiguous”,這裡是警告的級别,我們這裡選擇“Never Verify”,這時就沒有紅色的警告和錯誤提醒了。但是選擇這一項的前提是,我們能保證之後運作時我們可以加上足夠的constraints保證view的位置資訊完整。

以上3種方法就是我們在運作時給view增加constraints的解決辦法。

三.NSGridView

這是macOS給我們帶來的一個新的layout容器。

有時候我們為了維護constraints的正确性是件比較麻煩的事情,比如即使我們就是一組簡單的checkboxes,維護constraints也不容易。這個時候我們會選擇用stack view來讓我們開發更容易一些。

下圖是macOS的app常見到的一組checkboxes。

這時候我們選用NS/UIStackView來實作,因為它有以下的優點,它可以排列一組items,重要的是它可以處理好content size并且可以控制好每個item之間的spacing。

但是stack view依舊有一些場景無法很順手的處理。例如下圖的場景。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

這時依舊可以用stack view來實作,但是它不能幫我們根據content完成行和列的對齊。

這就是為什麼要引入新的NSGridView的原因。

使用NSGridView,我們可以很容易的做到content在X軸和Y軸上的對齊。僅僅隻需要我們把content放進預先定義好的網格中即可,NSGridView會幫我們管理好接下來對齊的一切事情。

我們來看看下面的例子。

NSGridView有2個子類,NSGridRow 和 NSGridColumn,它們倆會自動的管理好content的大小。當然我們可以在需要的時候指定size的大小,padding和spacing的大小。我們也可以動态的隐藏一些rows行和colunms列。

NSGridCell的工作就是管理每個cell裡面content view的layout。如果某個cell的内容超出cell的邊界,cell會合并起來,就像普通的電子表格app的做法一樣。

我們來建構一個簡單的界面。設計圖如下:

我們并不需要去關心網格的sizing,我們隻用關心每一行每一列究竟有多少個content需要被顯示出來。

let empty = NSGridCell.emptyContentView
let gridView = NSGridView(views: [
 [brailleTranslationLabel, brailleTranslationPopup], 
 [empty, showContractedCheckbox], 
 [empty, showEightDotCheckbox], 
 [statusCellsLabel, showGeneralDisplayCB],
 [empty, textStyleCB], 
 [showAlertCB] 
])複制代碼           

複制

用上述代碼運作出來的界面是這樣的:

雖然我們調用構造函數沒錯,但是出來的界面和設計的明顯有一些差距。最明顯的問題就是UI被拉開了,有很多空白的地方。

産生問題的原因就在于,網格被限制到了window的邊緣。我們的意圖應該是window來比對我們的網格大小,但是現在出現的問題變成了,網格被拉伸了,去比對window的大小了。

我們解決這個問題的辦法就是去改變 grid view内容的hugging的優先級。盡管頁面上的constraints已經具有了高優先級,但是我們現在仍可以繼續提高優先級,來讓constraints推動content,使其遠離window的邊緣。我們提高一些優先級:

gridView.setContentHuggingPriority(600, for: .horizontal)
gridView.setContentHuggingPriority(600, for: .vertical)複制代碼           

複制

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

我們會發現,window裡面的content更加聚合了,中間的大段空白消失了。

我們再來解決一下window中間的空白,左邊的label和右邊的content距離太遠。根據設計,我們應該讓label居右排列。這件事很容易,隻要我們調整一下cell的位置資訊即可完成。排列的位置資訊會影響到cell,行,列,網格視圖。

如果沒有指定cell的placement這個屬性值,那麼行列就會根據gridview的placement屬性值來确定。這個規則可以使我們在一處設定好placement,瞬間可以改變大量的cell的布局。

//first column needs to be right-justified:
gridView.column(at: 0).xPlacement = .trailing複制代碼           

複制

我們找到gridView的第一列,改變它的xPlacement屬性值,這樣一列的cell都會變成居右排列。

居右之後,我們又會出現新的問題,baseline不對齊了。

行的對齊和列的對齊原理一樣的,同理,我們隻需要設定一處,将會影響整個網格視圖。

// all cells use firstBaseline alignment
gridView.rowAlignment = .firstBaseline複制代碼           

複制

設定完成之後,整個網格視圖就對齊了。

接下來我們再來改變一下pop-up button的邊距。

let row = gridView.cell(for: brailleTranslationPopup)!.row!
row.topPadding = 5
row.bottomPadding = 5複制代碼           

複制

這裡取第一行的做法也可以和之前取第一列的做法一樣,直接取下标0的row即可。這裡換一種更好的做法來做。在gridView裡面找到包含pop-up button的cell,根據cell找到對應的row行。這種方式比直接去下标index的好處在于,日後如果有人在index 0的位置又增加了一行,那麼代碼就出錯了,而我們這裡的代碼一直都不會出錯,因為保證是取出了包含pop-up button的cell。是以代碼裡面盡量不要寫死固定的index,這樣以後維護起來比較困難。

同理,我們也給“status cells”也一起加上Padding

ridView.cell(for:statusCellsLabel)!.row!.topPadding = 6複制代碼           

複制

這裡需要對比一下padding 和 spacing的差別。

padding是針對每個行或者每個列之間的間距,我們可以增加padding來改變兩兩之間的間距。 spacing是針對整個gridview來說的,改變了它,将會影響整個網格視圖的布局。

再來看看我們的設計圖:

如果沒有padding那麼就是下圖的樣子:

如果沒有spacing那麼就會出現下圖的樣子:

如果spacing和padding都沒有的話,那就都擠在一起了:

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

最後我們來處理一下最下面那一行包含checkbox的cell

這裡就需要用到之前提到了,合并2個cell了。

// Special treatment for centered checkbox:
let cell = gridView.cell(for: showAlertCB)!
cell.row!.topPadding = 4
cell.row!.mergeCells(in: NSMakeRange(0, 2))複制代碼           

複制

這裡我們直接指出了,合并前2個cell。

執行完代碼之後,就會是這個樣子。

最後一行的cell就會橫跨2個cell的位置。雖然占了2個cell的位置,但是它依舊還繼承着第一列的居右的排列規則。

現在我們的需求是既不希望它居右,也不希望它居左。 checkbox其實是支援排列在2個列之間的,但是由于這相鄰的2個列的寬度并不相等,是以gridview不知道該怎麼排列了。這時就需要我們手動來改變布局了。

這裡可能有人會想,直接把

cell.xPlacement = .none複制代碼           

複制

把cell的xPlacement直接變成none,這樣做會一下子打亂整個gridview的constraints布局,我們不能這樣做。我們需要再繼續給cell加上額外的constraints來維護整個gridview的constraints的平衡。

cell.xPlacement = .none
let centering = showAlertCB.centerXAnchor.constraint(equalTo: textStyleCB.leadingAnchor)
cell.customPlacementConstraints = [centering]複制代碼           

複制

我們隻需要在給出checkbox在x軸方面的錨點即可。這時候checkbox就會排列成我們想要的樣子了。

至此,我們就完成了需求。總結一下,NSGridView是一個新的控件,能很好的幫助我們進行網格似布局。它能很快很友善的把我們需要展示的content排列整齊。之後我們僅僅隻需要調整一下padding和spacing這些資訊即可。

四.Layout Feedback Loop Debugging

有時候我們設定好了constraint之後,沒有報任何錯誤,但是有些情況當我們運作起來的時候就有一堆constraint沖突在debug視窗裡面,嚴重的還會使app直接崩潰。崩潰的情況就是遇到了layout feedback loop。

遇到這種情況,往往是發生在“過渡期”,開始或者結束的時候。如果說你點選了一個button,button相應了你的點選,但是之後button不彈起,一直保持着被按下的狀态。

然後會觀察到CPU使用率爆表,記憶體倍增,然後app就崩潰了,與此同時傳回了一大堆的layout的棧回溯資訊。

發生這個情況的原因是某個view的layout被一直執行,一直執行,陷入了死循環中。Runloop就不會停下,CPU的使用率會一直處于峰值。所有的消息都會被收集到自動釋放的對象中去,消息一直發送,就會一直收集。是以記憶體也會倍增。

導緻這個原因之一,是setNeedsLayout這個方法。

當其中一個view調用完setNeedsLayout之後,會傳遞到父視圖繼續調用setNeedsLayout,父視圖的setNeedsLayout可能又會調用到其他視圖的layout資訊。如果我們能在這互相之後調用找到調用者,也就是那個view調用了這個方法,那我們就可以分析清楚這些setNeedsLayout從哪裡來,到哪裡去,就能找到死循環的地方了。

這些資訊确實很難收集,這也是為何蘋果要為我們專門開發這樣一個工具,友善我們來調試,查找問題的原因。

開啟這個工具的開關在“Arguments”選項裡面。如下圖。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性
-UIViewLayoutFeedbackLoopDebuggingThreshold 100 // 50...1000
-NSViewLayoutFeedbackLoopDebuggingThreshold 100 // 50...1000
// Logs to com.apple.UIKit:LayoutLoop or com.apple.AppKit:LayoutLoop複制代碼           

複制

UIView是在iOS裡面使用的,NSView是在macOS裡面使用的。一旦我們開啟了這個開關,那麼layout feedback loop debugger就會開始記錄每一個調用了setNeedsLayout的資訊。

這裡我給它設定了閥值是100。

如果發現在一個Runloop中,layout在一個view上面調用的次數超過了閥值,這裡設定的是100,也就是說次數超過100,這個死循環還會在跑一小段,因為這個時候要給debugger一個記錄資訊的時間。當記錄完成之後,就會立即抛出異常。并且資訊會顯示在logs中。log會被記錄在com.apple.UIKit:LayoutLoop(iOS)/com.apple.AppKit:LayoutLoop(macOS)中

我們也可以打全局的異常斷點exception break point。 在調試視窗也可以用LLDB指令po出一些調試資訊。

接下來看2個實用的例子。

1.Upstream Geometry Change

這裡有這麼多個view,層級如上圖。

現在右子串上面10個子view在一次的層級變化中,被移除了。

那麼最上層圈起來的3個view都會被影響。于是這3個view的bounds就發生了變化。于是就會隐式的調用setNeedsLayout,來擷取新的bounds的資訊。(這裡經過@kuailejim @冬瓜争做全棧瓜 和大神們實驗,setNeedsLayout是需要我們開發者手動調用的,系統并不會在bounds改變的時候隐式調用setNeedsLayout方法)。目前view的bounds改變,但是如果父view沒有layout完成,那麼父view也會繼續收到setNeedsLayout消息。這個消息就會一直被往上傳遞,直到傳到最頂層的view,頂層的view layout完成之後,将會重置下面關聯的view的bounds,調用layoutSubview()方法。這時候,死循環就産生了。

這3個view就是上面3個view,下面的view需要setNeedsLayout,需要擷取最新的bounds資訊,中間藍色的view也同樣需要setNeedsLayout,于是又會讓上層的view調用setNeedsLayout()方法,這個時候死循環就産生了。上下各有2個環,共同的view就是中間藍色的view。環内的view都在互相的請求setNeedsLayout(),并且在自己layout完成以後又會去重置關聯的view的bounds。這就形成了triggers layout。

大家對這裡産生2個環産生了極大的好奇,熱烈讨論這裡會産生環的情況。目前可以想到會産生環的場景是這樣子的:在上面的3顆子樹,當某種場景下,突然删掉了右邊的子樹,假設使用者的螢幕現在是全屏,由于一下子突然删掉了一堆view,那麼原來那裡就會變成空白,這個時候開發者想要把其他的view平鋪到螢幕上。這個時候就需要改變上面父view的bounds,最下面的view會代碼裡面手動調用上面藍色的view,setNeedsLayout()方法,并且把藍色view的bounds設定成全屏,由于藍色view的bounds改變,這個時候開發者代碼裡面又手動調用了藍色view的父view,去執行setNeedsLayout()方法。top view代碼裡面又寫了bounds = origRect,這時候就觸發了藍色view的layout,更新bounds。這樣就産生了循環。同理下面也會形成循環。這樣就産生了2個死循環了。這些總結需要感謝@kuailejim @冬瓜争做全棧瓜 給出的指點。

這裡是我們用工具收集到的log,第一行就是top-level view,接下來的就是遞歸的過程。往下看,我們會看見一些數字,這些數字就是view接到layout的次數,并且這些數字是有序的。一次死循環中這些數字就是循環時候的順序。當然一個循環中,每個view可以是起點也可以是終點。這裡我們預設把top view設定成起點。這樣就可以向我們展示出死循環中一共牽扯進來了多少個view。

從log上看,上面有3個view,下面有10個view,加起來也不等于23,這是為什麼呢?我們繼續往下看log,來看看“Views receiving layout in order”這裡面記錄了些什麼吧。

這裡我們可以很明顯的看到,view接收到layout的順序,一共正好23個。也可以看出,在一起循環中,一個view接收到layout的次數不止一次。

如上圖所标示的,有2段在循環,有10個view接收到layout之後,再是2個view,緊接着又是10個view,再是1個view。

回到最初我們使用這個工具的用途上來,最初我們使用這個工具是用來檢視 top-level view 接收到setNeedsLayout消息到底從哪裡來。繼續往下查找,找到調用的棧資訊那裡。

從上往下看,前幾行肯定都是UIViewLayoutFeedbackLoopDebugging的資訊。往下看,看到第6行,可以看到DropShadowView接受到了資訊,準備setBounds。回看之前的層級資訊,我們會發現DropShadowView是TransitionView的子view。

引起DropShadowView觸發setBounds的唯一途徑是,它的父view,TransitionView觸發了setNeedsLayout()方法。因為這個時候TransitionView還沒有layout。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

回到“geometry change records”,這個時候我們可以看到選中的這3行資訊在一遍遍的循環。看第2行和第3行,我們可以看到是來自于TransitionView的layout。這時是合理的。再看第一行,會發現這個時候有一個TransitionView的子view調用了viewLayoutSubviews。

這個時候我們就定位到了bug的根源了,隻要想方設法在layout的時候,不要改變superview的bounds即可以去掉這個死循環。

2.Ambiguous Layout From Constraints

在我們設定constraints限制的時候,常常會産生一些歧義的constraints。歧義的constraints通常不可怕,我們隻需要稍稍做些調整,然後update all frame即可。

但是有如下的場景會導出形成環:

當你的view在旋轉之後,constraints也随之變化,然後有些view在旋轉之後的constraint就會互相沖突。因次有些constraint就形成了環。

這個問題在沒有這個debugger工具的時候,思考起來很燒腦,沒有任何頭緒,這也是為什麼log把top-level view放在第一行的原因,給我們暗示,從這裡開始找bug的原因。

在log,我們會看到好多的“Ambiguous Layout”。注意:tAMIC是Translates Auto Resizing Mask into Constraints的縮寫。

我們來看看詳細的log。看log之前,我們應該知道,constraint雖然沖突很多,但是可能引起沖突的constraint隻有一個,也就是說當我們更正了其中一個constraint,很可能所有的沖突都解決了。

如上圖log所示,在minX這裡我們設定了2個帶有沖突性的constraint,一個是-60,一個是-120。我們可以一個個的檢查限制,但是這個清單很長,檢查起來也比較麻煩。

那我們畫圖來分析一下這個問題。

WWDC2016 Session筆記 - Xcode 8 Auto Layout新特性

如圖,label有leading和trailing padding,label是container的子view,container是action的子父,action是representation的子view。container和action view之間有一個居中的centering constraint。action view在representation view上有一個autoresizing mask constraints。

然後每個representation view之間是alignment對齊的。自此看來,這些view并沒有足夠的constraints能讓這些view都能确定位置資訊。比如在X軸上,這一串view是可以存在在任何的位置,是以産生了歧義的constraint。

解決上面的歧義的

-UIViewLayoutFeedbackLoopDebuggingThreshold 100 // 50...1000
-NSViewLayoutFeedbackLoopDebuggingThreshold 100 // 50...1000
 //Logs to com.apple.UIKit:LayoutLoop or com.apple.AppKit:LayoutLoop複制代碼           

複制

用debugger就可以解決上述的問題。

總結

這個Xcode 8 給我們的Autolayout融合了之前Autoresizing masks的用法,使兩個合并在一起使用,這樣不同場景我們可以有更多的選擇,可以更加靈活的處理布局的問題。還允許我們能手動調節constraints警告優先級别。

針對macOS的布局問題,又給我們帶來了新的控件NSGridView

最後給我們帶來的新的調試Layout Feedback Loop Debugging的工具,能讓我們平時調試起來比較頭疼的問題,有了工具可以有據可循,迅速定位問題,查找問題。

最後,請大家多多指教。新浪微網誌@halfrost