3-3 Views--自定義控件

本文將討論一些自定義視圖眯分、控件的訣竅和技巧躏尉。我們先概述一下 UIKit 向我們提供的控件劳较,并介紹一些渲染技巧。隨后我們會深入到視圖和其所有者之間的通信策略蚜印,并簡略探討輔助功能莺禁,本地化和測試。

視圖層次概覽

如果你觀察一下 UIView 的子類窄赋,可以發(fā)現(xiàn) 3 個基類:reponders(響應者)哟冬,views(視圖)和controls(控件)楼熄。我們快速重溫一下它們之間發(fā)生了什么。

UIResponder

UIResponder是UIView的父類浩峡。responder能夠處理觸摸可岂、手勢、遠程控制等事件翰灾。之所以它是一個單獨的類而沒有合并到UIView中缕粹,是因為UIResponder有更多的子類,最明顯的就是UIApplication和UIViewController纸淮。通過重寫UIResponder的方法平斩,可以決定一個類是否可以成為第一響應者 (first responder),例如當前輸入焦點元素咽块。

當 touches (觸摸) 或 motion (指一系列運動傳感器) 等交互行為發(fā)生時绘面,它們被發(fā)送給第一響應者 (通常是一個視圖)。如果第一響應者沒有處理侈沪,則該行為沿著響應鏈到達視圖控制器揭璃,如果行為仍然沒有被處理,則繼續(xù)傳遞給應用亭罪。如果想監(jiān)測晃動手勢瘦馍,可以根據(jù)需要在這3層中的任意位置處理。

UIResponder還允許自定義輸入方法应役,從inputAccessoryView向鍵盤添加輔助視圖到使用inputView提供一個完全自定義的鍵盤情组。

UIView

UIView子類處理所有跟內容繪制有關的事情以及觸摸時間。只要寫過 "Hello, World" 應用的人都知道視圖扛吞,但我們重申一些技巧點:

一個普遍錯誤的概念:視圖的區(qū)域是由它的 frame 定義的呻惕。實際上 frame 是一個派生屬性荆责,是由center和bounds合成而來滥比。不使用 Auto Layout 時,大多數(shù)人使用 frame 來改變視圖的位置和大小做院。小心些盲泛,官方文檔特別詳細說明了一個注意事項:

如果 transform 屬性不是 identity transform 的話,那么這個屬性的值是未定義的键耕,因此應該將其忽略

另一個允許向視圖添加交互的方法是使用手勢識別寺滚。注意它們對 responders 并不起作用,而只對視圖及其子類奏效屈雄。

UIControl

UIControl建立在視圖上村视,增加了更多的交互支持。最重要的是酒奶,它增加了 target / action 模式蚁孔∧膛猓看一下具體的子類,我們可以看一下按鈕杠氢,日期選擇器 (Date pickers)站刑,文本框等等。創(chuàng)建交互控件時鼻百,你通常想要子類化一個UIControl绞旅。一些常見的像 bar buttons (雖然也支持 target / action) 和 text view (這里需要你使用代理來獲得通知) 的類其實并不是UIControl。

渲染

現(xiàn)在温艇,我們轉向可見部分:自定義渲染因悲。正如 Daniel 在他的文章中提到的,你可能想避免在 CPU 上做渲染而將其丟給 GPU勺爱。這里有一條經驗:盡量避免drawRect:囤捻,使用現(xiàn)有的視圖構建自定義視圖。

通常最快速的渲染方法是使用圖片視圖邻寿。例如蝎土,假設你想畫一個帶有邊框的圓形頭像,像下面圖片中這樣:

為了實現(xiàn)這個绣否,我們用以下的代碼創(chuàng)建了一個圖片視圖的子類:


我鼓勵各位讀者深入了解CALayer及其屬性誊涯,因為你用它能實現(xiàn)的大多數(shù)事情會比用 Core Graphics 自己畫要快。然而一如既往蒜撮,監(jiān)測自己的代碼的性能是十分重要的暴构。

把可拉伸的圖片和圖片視圖一起使用也可以極大的提高效率。在Taming UIButton這個帖子中段磨,Reda Lemeden 探索了幾種不同的繪圖方法取逾。在文章結尾處有一個很有價值的來自 UIKit 團隊的工程師 Andy Matuschak 的回復,解釋了可拉伸圖片是這些技術中最快的苹支。原因是可拉伸圖片在 CPU 和 GPU 之間的數(shù)據(jù)轉移量最小砾隅,并且這些圖片的繪制是經過高度優(yōu)化的。

處理圖片時债蜜,你也可以讓 GPU 為你工作來代替使用 Core Graphics晴埂。使用 Core Image,你不必用 CPU 做任何的工作就可以在圖片上建立復雜的效果寻定。你可以直接在 OpenGL 上下文上直接渲染儒洛,所有的工作都在 GPU 上完成。

自定義繪制

如果決定了采用自定義繪制狼速,有幾種不同的選項可供選擇琅锻。如果可能的話,看看是否可以生成一張圖片并在內存和磁盤上緩存起來。如果內容是動態(tài)的恼蓬,也許你可以使用 Core Animation沫浆,如果還是行不通,使用 Core Graphics滚秩。如果你真的想要接近底層专执,使用 GLKit 和原生 OpenGL 也不是那么難,但是需要做很多工作郁油。

如果你真的選擇了重寫drawRect:本股,確保檢查內容模式。默認的模式是將內容縮放以填充視圖的范圍桐腌,這在當視圖的 frame 改變時并不會重新繪制拄显。

自定義交互

正如之前所說的,自定義控件的時候案站,你幾乎一定會擴展一個 UIControl 的子類躬审。在你的子類里,可以使用 target action 機制觸發(fā)事件蟆盐,如下面的例子:


為了響應觸摸承边,你可能更傾向于使用手勢識別。然而如果想要更接近底層石挂,仍然可以重寫touchesBegan博助,touchesMoved和touchesEnded方法來訪問原始的觸摸行為。但雖說如此痹愚,創(chuàng)建一個手勢識別的子類來把手勢處理相關的邏輯從你的視圖或者視圖控制器中分離出來富岳,在很多情況下都是一種更合適的方式。

創(chuàng)建自定義控件時所面對的一個普遍的設計問題是向擁有它們的類中回傳返回值拯腮。比如窖式,假設你創(chuàng)建了一個繪制交互餅狀圖的自定義控件,想知道用戶何時選擇了其中一個部分动壤。你可以用很多種不同的方法來解決這個問題萝喘,比如通過 target action 模式,代理狼电,block 或者 KVO蜒灰,甚至通知。

使用 Target-Action

經典學院派的肩碟,通常也是最方便的做法是使用 target-action。在用戶選擇后你可以在自定義的視圖中做類似這樣的事情:


如果有一個視圖控制器在管理這個視圖凸椿,需要這樣做:


這么做的好處是在自定義視圖子類中需要做的事情很少削祈,并且自動獲得多目標支持。

使用代理

如果你需要更多的控制從視圖發(fā)送到視圖控制器的消息,通常使用代理模式髓抑。在我們的餅狀圖中咙崎,代碼看起來大概是這樣:


在視圖控制器中,你要寫如下代碼:


當你想要做更多復雜的工作而不僅僅是通知所有者值發(fā)生了變化時吨拍,這么做顯然更合適褪猛。不過雖然大多數(shù)開發(fā)人員可以非常快速的實現(xiàn)自定義代理羹饰,但這種方式仍然有一些缺點:你必須檢查代理是否實現(xiàn)了你想要調用的方法 (使用respondsToSelector:)伊滋,最重要的,通常你只有一個代理 (或者需要創(chuàng)建一個代理數(shù)組)队秩。也就是說笑旺,一旦視圖所有者和視圖之間的通信變得稍微復雜,我們幾乎總是會采取這種模式馍资。

使用 Block

另一個選擇是使用 block筒主。再一次用餅狀圖舉例,代碼看起來大概是這樣:


在選取行為的代碼中鸟蟹,你只需要執(zhí)行它乌妙。在此之前檢查一下block是否被賦值非常重要,因為執(zhí)行一個未被賦值的 block 會使程序崩潰建钥。


這種方法的好處是可以把相關的代碼整合在視圖控制器中:


就像代理冠胯,每個動作通常只有一個 block。另一個重要的限制是不要形成引用循環(huán)锦针。如果你的視圖控制器持有餅狀圖的強引用荠察,餅狀圖持有 block,block 又持有視圖控制器奈搜,就形成了一個引用循環(huán)悉盆。只要在 block 中引用 self 就會造成這個錯誤。所以通常代碼會寫成這個樣子:


一旦 block 中的代碼要失去控制 (比如 block 中要處理的事情太多馋吗,導致 block 中的代碼過多)焕盟,你還應該將它們抽離成獨立的方法,這種情況的話可能用代理會更好一些宏粤。

使用 KVO

如果喜歡 KVO脚翘,你也可以用它來觀察。這有一點神奇而且沒那么直接绍哎,但當應用中已經使用来农,它是很好的解耦設計模式。在餅狀圖類中崇堰,編寫代碼:


當使用合成屬性沃于,KVO 會攔截到該變化并發(fā)出通知涩咖。在視圖控制器中,編寫類似的代碼:


根據(jù)你的需要繁莹,在viewWillDisappear:或dealloc中檩互,還需要移除觀察者。對同一個對象設置多個觀察者很容易造成混亂咨演。有一些技術可以解決這個問題闸昨,比如ReactiveCocoa或者更輕量級的THObserversAndBinders

使用通知

作為最后一個選擇薄风,如果你想要一個非常松散的耦合饵较,可以使用通知來使其他對象得知變化。對于餅狀圖來說你幾乎肯定不想這樣村刨,不過為了講解的完整告抄,這里介紹如何去做。在餅狀圖的的頭文件中:


在實現(xiàn)文件中:


現(xiàn)在訂閱通知嵌牺,在視圖控制器中:


當添加了觀察者打洼,你可以不將餅狀圖作為參數(shù)object,而是傳遞nil逆粹,以接收所有餅狀圖對象發(fā)出的通知募疮。就像 KVO 通知,你也需要在恰當?shù)牡胤酵擞嗊@些通知僻弹。

這項技術的好處是完全的解耦阿浓。另一方面,你失去了類型安全蹋绽,因為在回調中你得到的是一個通知對象芭毙,而不像代理,編譯器無法檢查通知發(fā)送者和接受者之間的類型是否匹配卸耘。

輔助功能 (Accessibility)

蘋果官方提供的標準 iOS 控件均有輔助功能退敦。這也是推薦用標準控件創(chuàng)建自定義控件的另一個原因。

這或許可以作為一整期的主題蚣抗,但是如果你想編寫自定義視圖侈百,Accessibility Programming Guide說明了如何創(chuàng)建輔助控制器。最為值得注意的是翰铡,如果有一個視圖中有多個需要輔助功能的元素钝域,但它們并不是該視圖的子視圖,你可以讓視圖實現(xiàn)UIAccessibilityContainer協(xié)議锭魔。對于每一個元素例证,返回一個描述它的UIAccessibilityElement對象。

本地化

創(chuàng)建自定義視圖時赂毯,本地化也同樣重要战虏。像輔助功能一樣拣宰,這個可以作為一整期的話題党涕。本地化自定義視圖的最直接工作就是字符串內容烦感。如果使用NSString,你不必擔心編碼問題膛堤。如果在自定義視圖中展示日期或數(shù)字手趣,使用日期和數(shù)字格式化類來展示它們。使用NSLocalizedString本地化字符串肥荔。

另一個本地化過程中很有用的工具是 Auto Layout绿渣。例如,有在英文中很短的詞在德語中可能會很長燕耿。如果根據(jù)英文單詞的長度對視圖的尺寸做硬編碼中符,那么當翻譯成德文的時候幾乎一定會遇上麻煩。通過使用 Auto Layout誉帅,讓標簽控件自動調整為內容的尺寸淀散,并向依賴元素添加一些其他的限制以確保重新設置尺寸,使這項工作變得非常簡單蚜锨。蘋果為此提供了一個很好的介紹档插。另外,對于類似希伯來語這種順序從右到左的語言亚再,如果你使用了 leading 和 trailing 屬性郭膛,整個視圖會自動按照從右到左的順序展示,而不是硬編碼的從左至右氛悬。

測試

最后则剃,讓我們考慮測試視圖的問題。對于單元測試如捅,你可以使用 Xcode 自帶的工具或者其它第三方框架棍现。另外,可以使用 UIAutomation 或者其它基于它的工具伪朽。為此轴咱,你的視圖完全支持輔助功能是必要的。UIAutomation 并未充分得到利用的一個功能是截圖烈涮;你可以用它自動對比視圖和設計以確保兩者每一個像素都分毫不差朴肺。(插一個無關的小提示:你還可以使用它來為應用上架 App Store自動生成截圖,這在你有多個多國語言的應用時會特別有用)坚洽。


翻譯作者:migrant

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末戈稿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子讶舰,更是在濱河造成了極大的恐慌鞍盗,老刑警劉巖需了,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異般甲,居然都是意外死亡肋乍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門敷存,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墓造,“玉大人,你說我怎么就攤上這事锚烦∶倜觯” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵涮俄,是天一觀的道長蛉拙。 經常有香客問我,道長彻亲,這世上最難降的妖魔是什么孕锄? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮睹栖,結果婚禮上硫惕,老公的妹妹穿的比我還像新娘。我一直安慰自己野来,他們只是感情好恼除,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曼氛,像睡著了一般豁辉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舀患,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天徽级,我揣著相機與錄音,去河邊找鬼聊浅。 笑死餐抢,一個胖子當著我的面吹牛,可吹牛的內容都是我干的低匙。 我是一名探鬼主播旷痕,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼顽冶!你這毒婦竟也來了欺抗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤强重,失蹤者是張志新(化名)和其女友劉穎绞呈,沒想到半個月后贸人,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡佃声,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年艺智,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秉溉。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡姻蚓,死狀恐怖痰催,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情曲梗,我是刑警寧澤哮缺,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布弄跌,位于F島的核電站,受9級特大地震影響尝苇,放射性物質發(fā)生泄漏铛只。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一糠溜、第九天 我趴在偏房一處隱蔽的房頂上張望淳玩。 院中可真熱鬧,春花似錦非竿、人聲如沸蜕着。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽承匣。三九已至,卻和暖如春锤悄,著一層夾襖步出監(jiān)牢的瞬間韧骗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工零聚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袍暴,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓隶症,卻偏偏與公主長得像政模,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沿腰,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內容