你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> WWDC2016 Session筆記

WWDC2016 Session筆記

編輯:IOS開發基礎

1467784294969170.png

投稿文章,作者:一縷殇流化隱半邊冰霜(@halfrost)

目錄

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,當我們運行完程序,就會出現下圖的樣子。

1194012-637527eb1a0ca498.jpg

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

1194012-d716316a84356bf1.jpg

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

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

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

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

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

1194012-d66036d42faa95e9.png

上述的例子中,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的設置面板了。

1194012-8fa2f4a12705805d.png

1469178729196268.png

上圖就是在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。下面還可以選擇橫豎屏,和不用屏幕百分比的適應性。

1194012-ccd7ee3afb97128c.png

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

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

1194012-541263982f9d5004.png

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

1194012-a72576207ebe4447.png

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

1194012-c17df0eec79dd816.png

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

1194012-d8df43f7952321be.png

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

1194012-4df96205a8480096.png

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

1194012-55bf242088854780.png

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

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

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

1194012-79b7038d7df03eb8.png

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

1194012-d140165f6ca54036.png

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

1194012-02e1709fd25a60e9.png

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

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

1194012-793f4205c747a333.png

1194012-95aab84ae9baace3.png

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

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

1194012-48e850a152987d86.png

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

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

1469181750295553.png

1469181770543487.png

這個時候我們更新一下中間溫度的Label的字體大小,這時候計算變大,由於我們的constraints都是正確的,兩邊的View也會隨著Label字體變大而變大。

1469182474721896.png

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

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

1469182513552284.png

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

1194012-badadb886e9ff91e.png

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

1469182578705945.png

測試一下橫屏的效果

QQ截圖20160722181650.png

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

1194012-29ed778b3ce5e7aa.png

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的時候被移除。

QQ截圖20160722181843.png

當我們在運行時拿到圖片之後,這個是時候我們再給它加上適當的約束和長寬比例即可。

2.Intrinsic Content Size

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

1469182804901066.png

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產生歧義的時候,可以動態調整警告級別的能力。

1194012-4f433ddf72781326.png

在這個場景中,我們僅僅只知道我們需要把這個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。

1469182857410429.png

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

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

1469182878103994.png

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

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

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

我們來看看下面的例子。

1469182900354189.png

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

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

1469182945106103.png

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

1469182966187338.png

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

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

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

1469182988750981.png

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

產生問題的原因就在於,網格被約束到了window的邊緣。我們的意圖應該是window來匹配我們的網格大小,但是現在出現的問題變成了,網格被拉伸了,去匹配window的大小了。

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

gridView.setContentHuggingPriority(600, for: .horizontal)
gridView.setContentHuggingPriority(600, for: .vertical)

QQ截圖20160724215656.png

我們會發現,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都會變成居右排列。

QQ截圖20160724215631.png

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

QQ截圖20160724215619.png

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

// all cells use firstBaseline alignment
gridView.rowAlignment = .firstBaseline

QQ截圖20160724215603.png

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

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

QQ截圖20160724215548.png

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來說的,改變了它,將會影響整個網格視圖的布局。

再來看看我們的設計圖:

QQ截圖20160724215531.png

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

QQ截圖20160724215508.png

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

QQ截圖20160724215501.png

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

QQ截圖20160724215443.png

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

QQ截圖20160724215427.png

這裡就需要用到之前提到了,合並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。

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

QQ截圖20160724215409.png

最後一行的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就會排列成我們想要的樣子了。

QQ截圖20160724215349.png

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

四.Layout Feedback Loop Debugging

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

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

1469368407662584.png

然後會觀察到CPU使用率爆表,內存倍增,然後app就崩潰了,與此同時返回了一大堆的layout的棧回溯信息。

QQ截圖20160724215255.png

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

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

QQ截圖20160724215239.png

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

這些信息確實很難收集,這也是為何蘋果要為我們專門開發這樣一個工具,方便我們來調試,查找問題的原因。

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

QQ截圖20160724215214.png

-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出一些調試信息。

QQ截圖20160724215153.png

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

1.Upstream Geometry Change

1469368295678484.png

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

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

1469368265542392.png

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

QQ截圖20160724215027.png

這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 @冬瓜爭做全棧瓜 給出的指點。

QQ截圖20160724214945.png

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

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

QQ截圖20160724214906.png

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

QQ截圖20160724214843.png

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

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

QQ截圖20160724214816.png

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

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

QQ截圖20160724214754.png

回到“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的原因。

QQ截圖20160724214656.png

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

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

QQ截圖20160724214643.png

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

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

QQ截圖20160724214630.png

如圖,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的工具,能讓我們平時調試起來比較頭疼的問題,有了工具可以有據可循,迅速定位問題,查找問題。

最後,請大家多多指教。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved