UIKit 的 UIView 是一個(gè)非常重要的類涡驮,幾乎每個(gè)嘗試 iOS 開發(fā)的程序員都會(huì)用到它。UIView 本身實(shí)現(xiàn)了Composite Pattern虱黄,所以一個(gè)應(yīng)用的界面最終可以由一群樹狀組合的 UIView 來組合而成——在這棵 UIView 樹的最頂部,是繼承于 UIView 的 UIWindow 實(shí)例,然后是由 UIWindow 實(shí)例保有的 rootViewController 的根 UIView 實(shí)例临燃,然后是在該 UIView 實(shí)例上的各種各樣的子節(jié)點(diǎn) UIView。
父 UIView 可以擁有自己的子 UIView烙心,自然而然的膜廊,父 UIView 就會(huì)面對(duì)用怎樣的策略來布局、排列這些子 UIView 的問題淫茵。在 UIView 中爪瓜,UIKit 的開發(fā)者專門提供了layoutSubviews方法來解決這個(gè)問題。
官方文檔的描述
官方文檔對(duì)于該方法有如下的描述:
(This method) Lays out subviews.
Subclasses can override this method as needed to perform more precise layout of their subviews.You should override this method only if the autoresizing and constraint-based behaviors of the subviews DO NOT offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
以上節(jié)選自UIView Class Reference
Whenever the size of a view changes, UIKit applies the autoresizing behaviors of that view’s subviews andTHENcalls the layoutSubviews method of the view to let it make manual changes. You can implement the layoutSubviews method in custom views when the autoresizing behaviors by themselvesDO NOTyield the results you want. Your implementation of this method can do any of the following:
Adjust the size and position of any immediate subviews.
Add or remove subviews or Core Animation layers.
Force a subview to be redrawn by calling its setNeedsDisplay or setNeedsDisplayInRect: method.
One place where applications often lay out subviews manually is when implementing a large scrollable area. Because it is impractical to have a single large view for its scrollable content, applications often implement a root view that contains a number of smaller tile views. Each tile represents a portion of the scrollable content. When a scroll event happens, the root view calls its setNeedsLayout method to initiate a layout change. Its layoutSubviews method then repositions the tile views based on the amount of scrolling that occurred. As tiles scroll out of the view’s visible area, the layoutSubviews method moves the tiles to the incoming edge, replacing their contents in the process.
以上節(jié)選自View Programming Guide for iOS
從文檔的描述可以看到匙瘪,layoutSubviews 的主要功能就是讓程序員自己實(shí)現(xiàn)子 UIViews 的布局算法铆铆,從而在需要重新布局的時(shí)候,父 UIView 會(huì)按照這個(gè)流程重新布局自己的子 UIViews丹喻。而且薄货,layoutSubviews 方法只能被系統(tǒng)觸發(fā)調(diào)用,程序員不能手動(dòng)直接調(diào)用該方法碍论。要引起該方法的調(diào)用谅猾,可以調(diào)用 UIView 的setNeedsLayout方法來標(biāo)記一個(gè) UIView。這樣一來鳍悠,在 UI 線程的下次繪制循環(huán)中税娜,系統(tǒng)便會(huì)調(diào)用該 UIView 的 layoutSubviews 方法。
使用 layoutSubviews 的實(shí)例
一個(gè)比較典型的例子來自于RDVTabBarController項(xiàng)目贼涩,它的目標(biāo)是實(shí)現(xiàn)一個(gè)提供高定制性的 TabBarController巧涧。在這個(gè)項(xiàng)目中,作者使用了 layoutSubviews 來控制 TabBar 的子 UIView——TabBarItem 的重新布局遥倦,從而達(dá)到 TabBar 發(fā)生變化時(shí) TabBarItem 的繪制與布局不會(huì)出錯(cuò)的目的:
我在代碼中的注釋解釋了這段代碼都做了什么谤绳。如果你要實(shí)現(xiàn)自己的 layoutSubviews 方法的話,可以參考這個(gè)例子的流程袒哥。
何時(shí)被調(diào)用
一個(gè)曾經(jīng)讓我比較疑惑的問題是缩筛,既然我不能手動(dòng)直接調(diào)用該方法,那在什么時(shí)候堡称、何種條件下這個(gè)方法會(huì)被調(diào)用呢瞎抛?
Stackoverflow 上已經(jīng)有相關(guān)的討論了(作者在他的博客上有更詳細(xì)的描述),并且有一位朋友給出了很不錯(cuò)的解答:
init does not cause layoutSubviews to be called (duh)
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target
view setFrame intelligently calls layoutSubviews on the view having its frame set only if the size parameter of the frame is different
scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and its superview
rotating a device only calls layoutSubview on the parent view (the responding viewControllers primary view)
Resizing a view will call layoutSubviews on its superview
也就是說却紧,layoutSubviews 方法會(huì)在這些情況下桐臊,在這些 UIView 實(shí)例上被調(diào)用:
addSubview 被調(diào)用時(shí):target view(一定會(huì))胎撤,以及被添加的 view(第一次調(diào)用會(huì))
更改 UIView 的 frame 時(shí):被更改 frame 的 view(frame 與之前不同時(shí))
對(duì)于 UIScrollView 而言,滾動(dòng)式:scroll view
設(shè)備的 orientation 改變時(shí):涉及改變的 UIViewController 的 root view
使用 CGAffineTransformScale 改變 view 的 transform 屬性時(shí)断凶,view 的 superview:被改變的 view
然而伤提,根據(jù)我自己的實(shí)驗(yàn),上面的描述并不是很完善的认烁。我的兩點(diǎn)補(bǔ)充如下:
第一次調(diào)用 addSubview 的時(shí)候肿男,target view 和被添加到 target view 的 view 的 layoutSubviews 方法會(huì)被調(diào)用。在已經(jīng)添加完畢后却嗡,若 target view 已經(jīng)擁有該被添加 view舶沛,則只有 target view 的 layoutSubviews 方法會(huì)被調(diào)用〈凹郏“and all the subviews of the target”這句話是錯(cuò)誤的如庭。
只有 UIView 處于 key window 的 UIView 樹中時(shí),該 UIView 的 layoutSubviews 方法才有可能被調(diào)用撼港。不在樹中的不會(huì)被調(diào)用柱彻。這也是為什么 Stackoverflow 上的討論中這個(gè)答案的第二點(diǎn)會(huì)被提出。
小結(jié)
使用 layoutSubviews 可以讓應(yīng)用界面的適應(yīng)能力更強(qiáng)餐胀。如果 UIKit 默認(rèn)提供的自動(dòng)布局機(jī)制Auto Layout不能提供給你想要的 UIView 布局行為,你可以自己定制該方法來決定布局行為瘤载。