內(nèi)容來自于objc.io
可以說scroll view是iOS 生動特性的一個非常重要的注腳,而在完全掌握它之前稼锅,你甚至會有一種夜不能寐的感覺幢哨,接下來,讓我們細細剖析scrollview
由于scrollview的特性其實只是來自于UIView 特性的疊加哟冬,所以對scrollview的理解更多來自對UIView的理解楼熄,如下是兩過程的view渲染的細節(jié):
Rasterization and Composition
第一個步驟稱為光柵化,它只是執(zhí)行一些繪制指令并產(chǎn)生一份圖像浩峡。比如按鈕只是繪制一個圓角矩形可岂,并在中間繪制文字,這些內(nèi)容由view持有翰灾,等待交由第二個步驟使用缕粹。一旦每個view均執(zhí)行得到了光柵化圖像,則會使用稱為 合成 的過程將它們組合成屏幕大小的圖片纸淮。view層級在合成過程中起到了很重要的作用:子view會覆蓋在父view之上平斩。最頂層的view是window,而其合成的內(nèi)容是用戶最終看到的內(nèi)容咽块。
這個時候重點來了绘面,大家都知道view均有frame和bounds屬性,它們有相同的size(除了transform屬性所帶來的影響之外)侈沪,但orgin通常不一樣揭璃,理解這兩個屬性的原理就可以理解scrollView的原理。
在光柵化的過程中亭罪,view并不關心它的frame(決定view的位置和大惺葩伞)和在view層級中的位置(決定其合成的順序),而只關心自己的繪制內(nèi)容应役,繪制發(fā)生在每個view的drawRect方法中情组。
在drawRect調(diào)用之前,會為view創(chuàng)建一個空白的image以供繪制箩祥。這個image的坐標系統(tǒng)是其bounds呻惕,如果在bounds之外繪制,那這份繪制并不會成為光柵化圖像的一部分滥比,并會被廢棄亚脆。雖然ios底層的繪制過程使得可以將子view在superview的bounds之外渲染出來,但在光柵化的過程中盲泛,在bounds之外繪制的內(nèi)容是會被廢棄的濒持。
Scroll View’s Content Offset
以上這些跟scrollview有什么關系呢键耕?答案是關系大發(fā)啦。想象一下滾動的時候發(fā)生的事情:在拖拽過程中柑营,我們改變了view的frame屈雄,如果往右拽,會增加origin.x
之前在合成的時候計算子View在父view中的位置時官套,父view的bounds.origin 通常是{0,0}酒奶,所以子view.frame.origin即對應父view中對應坐標的點。但如果父view的bounds.origin不為0的時候奶赔,則需要將子view.frame.origin+父view.bounds.origin惋嚎。
所以更改bounds的origin可以調(diào)整子View在父view中顯示的位置,而且實際上站刑,scrollview的contentOffset屬性即是通過調(diào)整bounds.origin完成滾動的另伍。
Content Size
有了關于contentOffset的理解,接下來關注下contentSize
contentSize并不會更改scrollview 的bounds绞旅,所以不會影響Scrollview合成子view摆尝。scroll view的默認contentSize是{w:0,h:0},由于沒有可滾動的區(qū)域因悲,用戶不可以滾動堕汞,但scrollview仍然會在其bounds中顯示所有子view。
當contentSize比Scrollview大的時候晃琳,才允許滾動
上圖中visible area的bounds應當是{80,40,200,300}
當contentOffset為{0,0}時讯检,可見窗口的左上角正好是可滾動區(qū)域的左上角,這也是contentOffset的最小值蝎土,最大 contentOffset 是contentSize與Scrollview.bounds的差值视哑。
Tweaking the Window with Content Insets
contentInset可以改變contentOffset的最大和最小值(顯示上而已绣否,因為contentOffset的最小值仍是{0,0}誊涯,最大值亦不變)
contentInset看起來很有用,但為什么不直接更改contentSize呢蒜撮,以UITableView為例暴构,它已經(jīng)精準地根據(jù)各Cell的情況算出了contentSize《文ィ考慮使用UIRefreshControl的情況:不能將UIRefreshControl放在可滾動區(qū)域中取逾,因為這樣會使得用戶可以滾動經(jīng)過UIRefreshControl并停留在UIRefreshControl的上方,并無法主動彈回到第一個Cell的上邊界苹支。所以需要將UIRefreshControl放置在可滾動區(qū)域的上方砾隅,這樣使得contentOffset可以彈回第一行,而不是停留在UIRefreshControl上债蜜。
等一下晴埂,當滾動得夠遠以致于觸發(fā)了refresh時究反,這時tableview并沒有彈回第一行隱藏refreshControl是因為使用了contentInset。而當刷新結束時儒洛,會恢復contentInset精耐,此時contentOffset保持原值,且不需要對contentSize做新的計算琅锻,維持原值即可卦停,此時view恢復到將refreshControl隱藏。
那么在代碼中什么時候應該用到contentInset呢:一個極佳的例子是鍵盤出現(xiàn)的時候恼蓬。
當然還有zooming,今天不會討論這個惊完,但有一個有趣的地方可以注意下:從viewForZoomingInScrollView:返回的時候,檢查transform屬性滚秩,可以發(fā)現(xiàn)scrollview巧妙地運用了UIView現(xiàn)存的屬性专执。