View編程指南(二)


接上篇 View編程指南(一)

三、View的坐標系統

1. 坐標系統基礎


UIKit默認的坐標系統是從左上角開始的。坐標系統里的點使用float表示,這樣可以更加精確的布局和定位。圖1-4顯示了屏幕的坐標系統。除了屏幕的坐標系統之外,window和view有自己的本地坐標系統,這樣可以允許你相對于view和window來進行定位。

圖1-4

因為view和window都有他們自己的本地坐標系統,因此你需要時刻注意哪個坐標系統正在生效。當你進行繪制的時候,你的坐標是相對于view自己的坐標系統。當你進行幾何改變時,你的坐標是相對于superview的坐標系統。UIWindow和UIView都有幫助你進行坐標系統變換的方法,所以不要過于擔心。

其他的一些黑科技例如Core Graphics和OpenGL ES使用的是左下角為原點的坐標系,因此當你的代碼涉及到這兩套坐標系時,記得進行相應的變換!

2. Frame、Bounds和Center屬性之間的關系


  • frame屬性,它指定了當前view在其superview的坐標系統中的位置和大小
  • bounds屬性,它指定了當前view在其自身的本地坐標系統中的大小以及view的內容的原點在哪里
  • center屬性,它指出了當前view在其superview的坐標系統中的中心點位置

一般而言,你使用center和frame屬性來進行view的幾何操作。例如:如果你要改變view的位置,一般使用center屬性,因為center屬性幾乎在任何情況下都是可用的,即使view在拉伸、旋轉。

在繪制view時,一般會用到bounds屬性。默認bounds是以view的本地坐標系的0,0作為原點,大小和frame的大小相同。

圖1-5展示了這三者之間的關系

圖1-5

盡管你可以分別改變frame、bounds和center屬性,但事實上改變其中之一會影響到其他的屬性:

  • 當你設置frame屬性時,bounds屬性中的size值也會隨著frame屬性的size值改變。center屬性的值也會跟著改變以表示新的中心點
  • 當你設置center屬性時,你的frame的origin值會跟著改變
  • 當你設置bounds屬性的size值時,frame屬性的size值也會相應的改變

默認情況下,view的frame沒有clip到superview的frame,這樣如果subview有部分是繪制到superview的frame外的話,那部分也會進行繪制,雖然那部分不可見。你可以設置superview的clipsToBounds屬性為true。不管你有沒有設置clip,觸摸事件總是只會在可視區域里觸發,也就是說如果在subview繪制在superview外的區域里產生點擊事件,事件不會被傳遞到view里。

3. 坐標系統變換


坐標系統變換提供了一個方便快捷的方式來更改你的view或它的內容。仿射變換(affine transform)是指如何將某個坐標系統中的點映射到另一個坐標系統里。你可以用仿射變換來更改整個view的位置、大小或者它的朝向(orientation)。你也可以使用它來對繪制內容中的一部分來進行變換。如何使用仿射變換取決于你使用時的上下文:

  • 應用于整個view,直接設置view的transform屬性
  • 應用于繪制內容中的一部分,在你的drawRect方法中,將仿射變換應用于給定的graphices上下文

通常修改view的transform屬性是為了實現view的動畫。你一般不會使用transform來改變你的view的位置或者大小。為了設置view的位置或大小,我們通常直接修改view的frame屬性。

當修改view的transform屬性時,所有的變換都是根據view的中心點(center)來進行的。

在你的drawRect方法里,通常根據(0,0)這個原點來對需要繪制的內容進行布局并應用變換。這樣做的話,當你的view里的內容位置發生改變時,你只需要修改變換就可以了,這通常比重繪更快、性能更好。你可以通過CGContextGetCTM方法來活的graphics上下文關聯的仿射變換對象。

Current transformation matrix(CTM)對象是指你當前正在使用的仿射變換。當你對整個view操作時,CTM就是存儲在view的transform屬性里的那個值。

每個subview的坐標系統是基于它的superview的,也就是說當你更改一個view的transform屬性的時候,會影響到它所有的subview。但是這些改變只會影響到最終在屏幕上的繪制。因為每個view對于它的subview的繪制和布局都是根據它們自己的bounds,因此它會忽略superview的transform。

圖1-6展示了兩個不同的旋轉變換是如何組合在一起的。在view的drawRect方法里我們將圖形進行了45°的旋轉。在view上我們又進行了45°的旋轉。這樣圖形看上去被旋轉了90°。

圖1-6

如果一個view的transform屬性不是identity transform,view的frame屬性必須被忽略。你應當使用view的bounds和center屬性來獲取size和位置。

4. 點和像素


在iOS里,所有坐標值都使用浮點型的值,單位是。由于不同設備的像素密度是不同的,因此用點可以使用一種坐標值統一所有設備上的內容呈現。

當你確實需要使用圖片或者其他基于像素的技術,例如:OpenGL ES。iOS會幫你管理這些點到像素之間的映射(還記得imageset里1x、2x、3x的圖片么)。view也提供關于當前像素密度的信息,因此你可以根據這些信息來調整基于像素的繪制代碼以適應高分辨率屏幕。

四、View的運行時交互模型

當用戶和你的UI進行交互的時候或者當你編程來觸發一些事件的時候,UIKit會有一些列復雜的事件序列產生。UIKit在適當的時候會通知你的view來響應這些事件。圖1-7展示了從用戶點擊屏幕到最后圖形系統相應點擊并重繪屏幕的過程。

圖1-7

具體來說,當圖1-7的情況發生時,產生了下列的事件序列:

  1. 用戶點擊屏幕(呵呵)
  2. 硬件向UIKit Framework報告點擊事件
  3. UIKit Framework將點擊事件打包成UIEvent對象傳遞給合適的view
  4. 事件處理代碼響應事件,例如:
     * 改變view或者subview的屬性(frame, bounds, alpha等)
     * 調用setNeedsLayout方法將view或者subview標記為需要更新布局
     * 調用setNeedsDisplay或setNeedsDisplayInRect來標記view或者subview需要重繪
     * 通知controller需要更新一些數據
  5. 如果view的幾何外觀改變了,UIKit會根據下面的規則去改變它的subview
     a. 如果你有配置autoresizing規則,UIKit根據這些規則來調整view
     b. 如果view實現了layoutSubviews方法,UIKit會調用它
  6. 如果view的任何一部分被標記為需要重繪,UIKit會讓view重繪它。自定義view如果重載了drawRect方法,UIKit會調用它來進行重繪。
  7. 更新后的view和其他應用的可視內容會被發送到圖形硬件來進行顯示
  8. 圖形硬件將繪制好的內容發送到屏幕

以上更新模型的步驟主要適用于使用了標準系統view的應用。使用OpenGL ES來繪制的應用通常直接在整個屏幕上進行繪制,并且不需要布局subview。

在上面的步驟中,自定義view的主要可干預點如下:

  • 事件處理:
    -touchesBegin:withEvent
    -touchesMoved:withEvent
    -touchesEnded:withEvent
    -touchesCancelled:withEvent
  • layoutSubviews方法
  • drawRect方法

五、高效使用View的技巧

1. 盡可能少的進行自定義繪制


盡管自定義繪制在有些時候是必須的,但是它也是你應當盡可能避免的。只有當系統的view沒法提供你需要的外觀或者功能時,你才需要這么做。任何時候,如果你可以通過組合使用系統的view來達到你的目的,那你就應當把它們組合在一個自定義的view層級結構里,而不是自己實現一個自定義view

2. 盡可能的利用content mode


適當的content mode可以減少你重繪所需的時間。你應當盡量避免使用UIViewContentModeRedraw。因為不管你使用什么content mode,你都能通過調用setNeedsDisplay或setNeedsDisplayInRect來強制你的view進行重繪

3. 盡可能將你的view設置成不透明


UIKit使用每個view的opaque屬性來決定是否可以優化合成繪制操作。如果將這個值設置成true,UIKit就知道它不需要繪制任何這個view背后的內容(因為他們被你的view遮蓋,并且不可見)。

4. 當滾動時調整view的繪制策略


滾動會在短時間內造成多次的view繪制。如果你的繪制代碼沒有優化過,滾動時就會非常的卡頓。因此不要嘗試在滾動時保證你的view的內容都清晰可見,例如:你可以在滾動時將view的內容繪制質量臨時降低,當滾動結束時,你再使用高質量重新繪制你的內容。

5. 不要通過嵌入subview來自定義系統控件


盡管技術上可以往標準系統控件(那些從UIControl繼承的對象)里插入subview,但是請不要這么干!如果某個控件支持定制,它會在文檔里詳細說明,通過那些方法來定制是非常靠譜的。但是通過自己插入subview的方式來定制會造成你的應用在將來不可用(如果將來控件的實現機制改變了)。


下篇 View編程指南(三)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容