【iOS 開發(fā)】從 setNeedsLayout 說起

本文從 <code>setNeedsLayout</code> 這個方法說起蝙叛,分享與其相關(guān)的 UIKit 視圖交互植锉、使用場景等內(nèi)容。

UIKit 為 UIView 提供了這些方法來進行視圖的更新與重繪:

public func setNeedsLayout()
public func layoutSubviews()
public func layoutIfNeeded()

public func setNeedsDisplay()
public func setNeedsDisplayInRect(rect: CGRect)
public func drawRect(rect: CGRect)

運行時視圖交互模型

無論是用戶交互觸發(fā)還是代碼自動觸發(fā),下圖展示的事件序列都同樣適用,這里用到了 <code>setNeedsLayout</code> 方法:

UIKit interactions with your view objects

上圖對應(yīng)的事件序列如下:

  1. 用戶觸摸屏幕
  2. 硬件報告觸摸事件給 UIKit 框架
  3. UIKit 框架將觸摸事件打包成 UIEvent 對象砂代,然后分發(fā)給合適的視圖
  4. 事件處理代碼會對相應(yīng)事件作出響應(yīng),代碼可以是這樣的:
    -更改 <code>frame</code>率挣、<code>bounds</code>刻伊、<code>alpha</code> 等屬性
    -調(diào)用 <code>setNeedsLayout</code> 方法以標(biāo)記該視圖(或者它的子視圖)為需要進行布局更新
    -調(diào)用 <code>setNeedsDisplay</code> 或者 <code>setNeedsDisplayInRect: </code> 方法以標(biāo)記該視圖(或者它的子視圖)需要進行重畫
    -通知 Controller 有數(shù)據(jù)變化
  5. 如果一個視圖的幾何結(jié)構(gòu)改變了,UIKit 會更新它的子視圖
  6. 如果任何視圖的任何部分被標(biāo)記為需要重畫椒功,UIKit 會要求視圖重畫自身
  7. 任何已經(jīng)更新的視圖會與應(yīng)用余下的可視內(nèi)容組合在一起捶箱,同時被發(fā)送到圖形硬件去顯示
  8. 圖形硬件將已解釋內(nèi)容轉(zhuǎn)化到屏幕上

方法調(diào)用邏輯

在上面的過程中,我們可以接觸到文章開頭提到的方法动漾,他們的調(diào)用邏輯是這樣的:

<code>setNeedsLayout</code> 會給當(dāng)前 UIView 立一個 flag丁屎,以表示后續(xù)應(yīng)該調(diào)用 <code>layoutSubviews</code> 方法,以調(diào)整當(dāng)前視圖及其子視圖的布局谦炬。

<code>setNeedsDisplayInRect: </code> 會給當(dāng)前 UIView 立一個 flag悦屏,以表示后續(xù)應(yīng)該調(diào)用 <code>drawRect:</code> 方法节沦,以進行視圖重繪键思。


View Drawing Cycle

Apple 官方文檔已經(jīng)明確說明,開發(fā)者不應(yīng)該直接調(diào)用 <code>layoutSubviews</code> 與 <code>drawRect:</code> 甫贯,而應(yīng)該在你認(rèn)為系統(tǒng)默認(rèn)的布局和重繪不能帶給你想要的效果時吼鳞,在子類中重寫這些方法,然后分別通過 <code>setNeedsLayout</code> 和 <code>setNeedsDisplayInRect: </code> 來進行調(diào)用叫搁。

當(dāng)然你可以給多個 UIView 設(shè)置 <code>setNeedsLayout</code>赔桌,然后當(dāng)下一個 View Drawing Cycle 到來時供炎,多個 UIView 的視圖會一同更改布局。

那么這個 View Drawing Cycle 到底是什么呢疾党,官方是這樣解釋的:

The system waits until the end of the current run loop before initiating any drawing operations. This delay gives you a chance to invalidate multiple views, add or remove views from your hierarchy, hide views, resize views, and reposition views all at once. All of the changes you make are then reflected at the same time.

顯然這樣用 RunLoop 把多次修改聚集在一個 Cycle 一并進行渲染是更加高效的行為音诫。

(我個人對 View Drawing Cycle 的理解是這樣的:UIKit 需要處理非常多的事件,這些事件組合起來變成了一個非常復(fù)雜的事件序列雪位,在這個序列中有些特定的點是 UIKit 專門提供給 UIView 來進行視圖更改的竭钝。如上所述,在當(dāng)前 run loop 結(jié)束之前雹洗,我們有機會做各種視圖更改香罐,并且這些更改會在下一個 run loop 體現(xiàn)出來,所以** View Drawing Cycle 就是一次次 run loop 中我們通過 UIKit 得到的 UIView 重布局时肿、重繪機會所組成的循環(huán)**庇茫。有理解不對的地方,歡迎評論指正螃成。)


如何善用 View Drawing Cycle

一個很常見的例子是旦签,一個 iPad App,橫屏和豎屏?xí)r界面布局不一樣寸宏,那么你可以監(jiān)聽設(shè)備旋轉(zhuǎn)顷霹,在設(shè)備旋轉(zhuǎn)時執(zhí)行 <code>setNeedsLayout</code> 方法,然后在 <code>layoutSubviews</code> 里面通過判斷接下來是橫屏還是豎屏來進行不一樣的布局設(shè)置击吱×艿恚基本上你不可能只在這個方法里只進行了單個 UIView 的布局修改,而是多項修改覆醇,那么 App 會在下一個 View Drawing Cycle 到來時朵纷,把這些修改一起執(zhí)行,這是最正常的情況永脓。

那么假如我不按 Apple 規(guī)定的來袍辞,直接調(diào)用 <code>layoutSubviews</code> 呢?我們可以猜想一下:因為這個方法里面提供了我們需要的布局方式常摧,所以 UIView 會按我們想要的方式來布局搅吁,但是因為各種視圖修改的請求時機是零碎的,所以這樣效率會低一些落午。所以重要的其實是了解何時會觸發(fā) <code>layoutSubviews</code>:

  • init 初始化不會觸發(fā) layoutSubviews
  • addSubview 會觸發(fā) layoutSubviews
  • 設(shè)置 view 的 frame 會觸發(fā) layoutSubviews谎懦,當(dāng)然前提是 frame 的值設(shè)置前后發(fā)生了變化
  • 滾動一個 UIScrollView 會觸發(fā) layoutSubviews
  • 旋轉(zhuǎn) Screen 會觸發(fā)父 UIView 上的 layoutSubviews 事件
  • 改變一個 UIView 大小的時候也會觸發(fā)父 UIView 上的 layoutSubviews 事件

然后按 Apple 要求的方式來做就好了(分別通過 <code>setNeedsLayout</code> 和 <code>setNeedsDisplayInRect: </code> 來調(diào)用 <code>layoutSubviews</code> 和 <code>drawRect:</code>)

但有些情況比較特殊:你打開 iOS 的時鐘應(yīng)用,去看里面的秒表頁面溃斋,這個頁面里面的兩個按鈕是沒有 UIButton 默認(rèn)的動畫的界拦,點擊之后,按鈕會瞬間改變自身的狀態(tài)(顏色梗劫、內(nèi)部 Label 的內(nèi)容)享甸,這種情況我們需要跳出 View Drawing Cycle截碴,來實現(xiàn)一個瞬間改變的效果。實現(xiàn)方法如下:

extension UIButton {
    func quickButtonAction() {
        UIView.performWithoutAnimation({
            // do something
            self.layoutIfNeeded()
        })
    }
}

可以看出 <code>layoutIfNeeded</code> 作為一個輔助選項給了 <code>setNeedsLayout</code> 一個可以瞬時執(zhí)行的特點蛉威。當(dāng)然默認(rèn)這個“選項”是關(guān)閉的日丹。


setNeedsDisplay 補充

<code>setNeedsLayout</code> 的使用場景之前已經(jīng)提過了(iPad App),下面舉個栗子說一下 <code>setNeedsDisplayInRect: </code>的使用場景蚯嫌。

假如我需要在兩點之間繪制一條直線聚凹,有兩個 <code>dotView</code>,需要繪制一個 <code>lineView</code>齐帚。我在 <code>drawRect:</code> 方法里實現(xiàn)了 <code>lineView</code> 的具體繪制方法(根據(jù)兩個點來繪制)妒牙。那么如果我想要這個直線一直根據(jù)兩個點同步變化的話,就需要在 <code>dotView</code> 的位置發(fā)生改變時对妄,執(zhí)行:

lineView.setNeedsDisplay() // 重繪 lineView

至于 <code>drawRect:</code> 方法什么時候會被觸發(fā):

From StackOverFlow

一個很好的參考鏈接:What is the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末湘今,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剪菱,更是在濱河造成了極大的恐慌摩瞎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孝常,死亡現(xiàn)場離奇詭異旗们,居然都是意外死亡,警方通過查閱死者的電腦和手機构灸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門上渴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人喜颁,你說我怎么就攤上這事稠氮。” “怎么了半开?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵隔披,是天一觀的道長。 經(jīng)常有香客問我寂拆,道長奢米,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任纠永,我火速辦了婚禮鬓长,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渺蒿。我一直安慰自己痢士,他們只是感情好彪薛,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布茂装。 她就那樣靜靜地躺著怠蹂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪少态。 梳的紋絲不亂的頭發(fā)上城侧,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音彼妻,去河邊找鬼嫌佑。 笑死,一個胖子當(dāng)著我的面吹牛侨歉,可吹牛的內(nèi)容都是我干的屋摇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼幽邓,長吁一口氣:“原來是場噩夢啊……” “哼炮温!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牵舵,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤柒啤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后畸颅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體担巩,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年没炒,在試婚紗的時候發(fā)現(xiàn)自己被綠了涛癌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡送火,死狀恐怖祖很,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漾脂,我是刑警寧澤假颇,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站骨稿,受9級特大地震影響笨鸡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坦冠,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一形耗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辙浑,春花似錦激涤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽送滞。三九已至,卻和暖如春辱挥,著一層夾襖步出監(jiān)牢的瞬間犁嗅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工晤碘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留褂微,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓园爷,卻偏偏與公主長得像宠蚂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子童社,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容