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,簡化咱們的步驟,並且能讓咱們的設置起來更加靈活。 git

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

有這樣一種場景,試想,咱們把一個view放在父view上,這個時候並無設置constraints,當咱們運行完程序,就會出現下圖的樣子。 api

看上去一切都還正常。可是一旦當咱們把設備旋轉90°之後,就會出現下圖的樣子。app

這個時候能夠發現,這個View的長,寬,以及top和left的邊距都沒有發生變化。這時咱們並無設置constraints,這是怎麼作到的呢?ide

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

若是咱們須要更加動態的resize的行爲,就須要咱們在IB裏面自定義約束了。如今問題就來了,有沒有更好的方式來作這件事情?最好是能有一種不用約束的方法,也能達到簡單的resize的效果。工具

如今這個問題有了解決辦法。在Xcode8中,咱們能夠給View指定autoresizing masks,而不用去設置constraints。這就意味着咱們能夠不用約束,咱們也能作到簡單的resize的效果。oop

在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

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

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

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

緣由是由於Autoresizing masks並不會向Autolayout同樣,會考慮View的content,因此這裏被擠壓了。

想fix這個Label,咱們能夠很容易的添加一個constraints來修復。不過這裏咱們來談談另一種作法。

進入到Attributes Inspector面板,找到Autoshrink屬性,把「fixed font size」切換成「minimum font size」

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

此時就算是回到landscape,分屏的狀況下,已經能夠顯示正常。

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

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

咱們一次性選擇「Center Horizontally in Container」 和 「Center Vertically in Container」。注意這個時候右邊仍是AutoresizingMask的面板,由於這個時候Label尚未任何的constraint。當咱們點擊「Add Constraints」的時候,就給Label加上了約束,右邊的面板也變成了constraints面板了。

咱們再給這個Label繼續加2個constraints。「Horizontal Spacing」和「Baseline」。

一樣的,從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放到背景去。這時,咱們全部的界面就佈局完成了。

測試一下橫屏的效果

甚至分屏的同樣能夠完成任務!

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的大小。

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依舊有一些場景沒法很順手的處理。例以下圖的場景。

這時依舊能夠用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)複製代碼

咱們會發現,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都沒有的話,那就都擠在一塊兒了:

最後咱們來處理一下最下面那一行包含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」選項裏面。以下圖。

-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。

回到「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。咱們能夠一個個的檢查約束,可是這個列表很長,檢查起來也比較麻煩。

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

如圖,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

相關文章
相關標籤/搜索