在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 自定義控件
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開(kāi)發(fā)
Collection View 動(dòng)畫(huà)
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫(huà)
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫(huà)
常見(jiàn)的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫(huà)
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺(jué)
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開(kāi)發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

自定義控件

本文將討論一些自定義視圖、控件的訣竅和技巧。我們先概述一下 UIKit 向我們提供的控件,并介紹一些渲染技巧。隨后我們會(huì)深入到視圖和其所有者之間的通信策略,并簡(jiǎn)略探討輔助功能,本地化和測(cè)試。

視圖層次概覽

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

UIResponder

UIResponderUIView 的父類。responder 能夠處理觸摸、手勢(shì)、遠(yuǎn)程控制等事件。之所以它是一個(gè)單獨(dú)的類而沒(méi)有合并到 UIView 中,是因?yàn)?UIResponder 有更多的子類,最明顯的就是 UIApplicationUIViewController。通過(guò)重寫(xiě) UIResponder 的方法,可以決定一個(gè)類是否可以成為第一響應(yīng)者 (first responder),例如當(dāng)前輸入焦點(diǎn)元素。

當(dāng) touches (觸摸) 或 motion (指一系列運(yùn)動(dòng)傳感器) 等交互行為發(fā)生時(shí),它們被發(fā)送給第一響應(yīng)者 (通常是一個(gè)視圖)。如果第一響應(yīng)者沒(méi)有處理,則該行為沿著響應(yīng)鏈到達(dá)視圖控制器,如果行為仍然沒(méi)有被處理,則繼續(xù)傳遞給應(yīng)用。如果想監(jiān)測(cè)晃動(dòng)手勢(shì),可以根據(jù)需要在這3層中的任意位置處理。

UIResponder 還允許自定義輸入方法,從 inputAccessoryView 向鍵盤(pán)添加輔助視圖到使用 inputView 提供一個(gè)完全自定義的鍵盤(pán)。

UIView

UIView 子類處理所有跟內(nèi)容繪制有關(guān)的事情以及觸摸時(shí)間。只要寫(xiě)過(guò) "Hello, World" 應(yīng)用的人都知道視圖,但我們重申一些技巧點(diǎn):

一個(gè)普遍錯(cuò)誤的概念:視圖的區(qū)域是由它的 frame 定義的。實(shí)際上 frame 是一個(gè)派生屬性,是由 centerbounds 合成而來(lái)。不使用 Auto Layout 時(shí),大多數(shù)人使用 frame 來(lái)改變視圖的位置和大小。小心些,官方文檔特別詳細(xì)說(shuō)明了一個(gè)注意事項(xiàng):

如果 transform 屬性不是 identity transform 的話,那么這個(gè)屬性的值是未定義的,因此應(yīng)該將其忽略

另一個(gè)允許向視圖添加交互的方法是使用手勢(shì)識(shí)別。注意它們對(duì) responders 并不起作用,而只對(duì)視圖及其子類奏效。

UIControl

UIControl 建立在視圖上,增加了更多的交互支持。最重要的是,它增加了 target / action 模式??匆幌戮唧w的子類,我們可以看一下按鈕,日期選擇器 (Date pickers),文本框等等。創(chuàng)建交互控件時(shí),你通常想要子類化一個(gè) UIControl。一些常見(jiàn)的像 bar buttons (雖然也支持 target / action) 和 text view (這里需要你使用代理來(lái)獲得通知) 的類其實(shí)并不是 UIControl。

渲染

現(xiàn)在,我們轉(zhuǎn)向可見(jiàn)部分:自定義渲染。正如 Daniel 在他的文章中提到的,你可能想避免在 CPU 上做渲染而將其丟給 GPU。這里有一條經(jīng)驗(yàn):盡量避免 drawRect:,使用現(xiàn)有的視圖構(gòu)建自定義視圖。

通常最快速的渲染方法是使用圖片視圖。例如,假設(shè)你想畫(huà)一個(gè)帶有邊框的圓形頭像,像下面圖片中這樣:

http://wiki.jikexueyuan.com/project/objc/images/3-11.png" alt="" />

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

// called from initializer
- (void)setupView
{
    self.clipsToBounds = YES;
    self.layer.cornerRadius = self.bounds.size.width / 2;
    self.layer.borderWidth = 3;
    self.layer.borderColor = [UIColor darkGrayColor].CGColor;
}

我鼓勵(lì)各位讀者深入了解 CALayer 及其屬性,因?yàn)槟阌盟軐?shí)現(xiàn)的大多數(shù)事情會(huì)比用 Core Graphics 自己畫(huà)要快。然而一如既往,監(jiān)測(cè)自己的代碼的性能是十分重要的。

把可拉伸的圖片和圖片視圖一起使用也可以極大的提高效率。在 Taming UIButton 這個(gè)帖子中,Reda Lemeden 探索了幾種不同的繪圖方法。在文章結(jié)尾處有一個(gè)很有價(jià)值的來(lái)自 UIKit 團(tuán)隊(duì)的工程師 Andy Matuschak 的回復(fù),解釋了可拉伸圖片是這些技術(shù)中最快的。原因是可拉伸圖片在 CPU 和 GPU 之間的數(shù)據(jù)轉(zhuǎn)移量最小,并且這些圖片的繪制是經(jīng)過(guò)高度優(yōu)化的。

處理圖片時(shí),你也可以讓 GPU 為你工作來(lái)代替使用 Core Graphics。使用 Core Image,你不必用 CPU 做任何的工作就可以在圖片上建立復(fù)雜的效果。你可以直接在 OpenGL 上下文上直接渲染,所有的工作都在 GPU 上完成。

自定義繪制

如果決定了采用自定義繪制,有幾種不同的選項(xiàng)可供選擇。如果可能的話,看看是否可以生成一張圖片并在內(nèi)存和磁盤(pán)上緩存起來(lái)。如果內(nèi)容是動(dòng)態(tài)的,也許你可以使用 Core Animation,如果還是行不通,使用 Core Graphics。如果你真的想要接近底層,使用 GLKit 和原生 OpenGL 也不是那么難,但是需要做很多工作。

如果你真的選擇了重寫(xiě) drawRect:,確保檢查內(nèi)容模式。默認(rèn)的模式是將內(nèi)容縮放以填充視圖的范圍,這在當(dāng)視圖的 frame 改變時(shí)并不會(huì)重新繪制。

自定義交互

正如之前所說(shuō)的,自定義控件的時(shí)候,你幾乎一定會(huì)擴(kuò)展一個(gè) UIControl 的子類。在你的子類里,可以使用 target action 機(jī)制觸發(fā)事件,如下面的例子:

[self sendActionsForControlEvents:UIControlEventValueChanged];

為了響應(yīng)觸摸,你可能更傾向于使用手勢(shì)識(shí)別。然而如果想要更接近底層,仍然可以重寫(xiě) touchesBegan, touchesMovedtouchesEnded 方法來(lái)訪問(wèn)原始的觸摸行為。但雖說(shuō)如此,創(chuàng)建一個(gè)手勢(shì)識(shí)別的子類來(lái)把手勢(shì)處理相關(guān)的邏輯從你的視圖或者視圖控制器中分離出來(lái),在很多情況下都是一種更合適的方式。

創(chuàng)建自定義控件時(shí)所面對(duì)的一個(gè)普遍的設(shè)計(jì)問(wèn)題是向擁有它們的類中回傳返回值。比如,假設(shè)你創(chuàng)建了一個(gè)繪制交互餅狀圖的自定義控件,想知道用戶何時(shí)選擇了其中一個(gè)部分。你可以用很多種不同的方法來(lái)解決這個(gè)問(wèn)題,比如通過(guò) target action 模式,代理,block 或者 KVO,甚至通知。

使用 Target-Action

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

[self sendActionsForControlEvents:UIControlEventValueChanged];

如果有一個(gè)視圖控制器在管理這個(gè)視圖,需要這樣做:

- (void)setupPieChart
{
    [self.pieChart addTarget:self 
                  action:@selector(updateSelection:)
        forControlEvents:UIControlEventValueChanged];
}

- (void)updateSelection:(id)sender
{
    NSLog(@"%@", self.pieChart.selectedSector);
}

這么做的好處是在自定義視圖子類中需要做的事情很少,并且自動(dòng)獲得多目標(biāo)支持。

使用代理

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

[self.delegate pieChart:self didSelectSector:self.selectedSector];

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

@interface MyViewController <PieChartDelegate>

 ...

- (void)setupPieChart
{
    self.pieChart.delegate = self;
}

- (void)pieChart:(PieChart*)pieChart didSelectSector:(PieChartSector*)sector
{
    // 處理區(qū)塊
}

當(dāng)你想要做更多復(fù)雜的工作而不僅僅是通知所有者值發(fā)生了變化時(shí),這么做顯然更合適。不過(guò)雖然大多數(shù)開(kāi)發(fā)人員可以非??焖俚膶?shí)現(xiàn)自定義代理,但這種方式仍然有一些缺點(diǎn):你必須檢查代理是否實(shí)現(xiàn)了你想要調(diào)用的方法 (使用 respondsToSelector:),最重要的,通常你只有一個(gè)代理 (或者需要?jiǎng)?chuàng)建一個(gè)代理數(shù)組)。也就是說(shuō),一旦視圖所有者和視圖之間的通信變得稍微復(fù)雜,我們幾乎總是會(huì)采取這種模式。

使用 Block

另一個(gè)選擇是使用 block。再一次用餅狀圖舉例,代碼看起來(lái)大概是這樣:

@interface PieChart : UIControl

@property (nonatomic,copy) void(^selectionHandler)(PieChartSection* selectedSection);

@end

在選取行為的代碼中,你只需要執(zhí)行它。在此之前檢查一下block是否被賦值非常重要,因?yàn)閳?zhí)行一個(gè)未被賦值的 block 會(huì)使程序崩潰。

if (self.selectionHandler != NULL) {
    self.selectionHandler(self.selectedSection);
}

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

- (void)setupPieChart
{
    self.pieChart.selectionHandler = ^(PieChartSection* section) {
        // 處理區(qū)塊
    }
}

就像代理,每個(gè)動(dòng)作通常只有一個(gè) block。另一個(gè)重要的限制是不要形成引用循環(huán)。如果你的視圖控制器持有餅狀圖的強(qiáng)引用,餅狀圖持有 block,block 又持有視圖控制器,就形成了一個(gè)引用循環(huán)。只要在 block 中引用 self 就會(huì)造成這個(gè)錯(cuò)誤。所以通常代碼會(huì)寫(xiě)成這個(gè)樣子:

__weak id weakSelf = self;
self.pieChart.selectionHandler = ^(PieChartSection* section) {
    MyViewController* strongSelf = weakSelf;
    [strongSelf handleSectionChange:section];
}

一旦 block 中的代碼要失去控制 (比如 block 中要處理的事情太多,導(dǎo)致 block 中的代碼過(guò)多),你還應(yīng)該將它們抽離成獨(dú)立的方法,這種情況的話可能用代理會(huì)更好一些。

使用 KVO

如果喜歡 KVO,你也可以用它來(lái)觀察。這有一點(diǎn)神奇而且沒(méi)那么直接,但當(dāng)應(yīng)用中已經(jīng)使用,它是很好的解耦設(shè)計(jì)模式。在餅狀圖類中,編寫(xiě)代碼:

self.selectedSegment = theNewSelectedSegment;

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

- (void)setupPieChart
{
    [self.pieChart addObserver:self forKeyPath:@"selectedSegment" options:0 context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{
    if(object == self.pieChart && [keyPath isEqualToString:@"selectedSegment"]) {
        // 處理改變
    }
}

根據(jù)你的需要,在 viewWillDisappear:dealloc 中,還需要移除觀察者。對(duì)同一個(gè)對(duì)象設(shè)置多個(gè)觀察者很容易造成混亂。有一些技術(shù)可以解決這個(gè)問(wèn)題,比如 ReactiveCocoa 或者更輕量級(jí)的 THObserversAndBinders。

使用通知

作為最后一個(gè)選擇,如果你想要一個(gè)非常松散的耦合,可以使用通知來(lái)使其他對(duì)象得知變化。對(duì)于餅狀圖來(lái)說(shuō)你幾乎肯定不想這樣,不過(guò)為了講解的完整,這里介紹如何去做。在餅狀圖的的頭文件中:

extern NSString* const SelectedSegmentChangedNotification;

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

NSString* const SelectedSegmentChangedNotification = @"selectedSegmentChangedNotification";

...

- (void)notifyAboutChanges
{
    [[NSNotificationCenter defaultCenter] postNotificationName:SelectedSegmentChangedNotification object:self];
}

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

- (void)setupPieChart
{
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                       selector:@selector(segmentChanged:) 
                                           name:SelectedSegmentChangedNotification
                                          object:self.pieChart];

}

...

- (void)segmentChanged:(NSNotification*)note
{
}

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

這項(xiàng)技術(shù)的好處是完全的解耦。另一方面,你失去了類型安全,因?yàn)樵诨卣{(diào)中你得到的是一個(gè)通知對(duì)象,而不像代理,編譯器無(wú)法檢查通知發(fā)送者和接受者之間的類型是否匹配。

輔助功能 (Accessibility)

蘋(píng)果官方提供的標(biāo)準(zhǔn) iOS 控件均有輔助功能。這也是推薦用標(biāo)準(zhǔn)控件創(chuàng)建自定義控件的另一個(gè)原因。

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

本地化

創(chuàng)建自定義視圖時(shí),本地化也同樣重要。像輔助功能一樣,這個(gè)可以作為一整期的話題。本地化自定義視圖的最直接工作就是字符串內(nèi)容。如果使用 NSString,你不必?fù)?dān)心編碼問(wèn)題。如果在自定義視圖中展示日期或數(shù)字,使用日期和數(shù)字格式化類來(lái)展示它們。使用 NSLocalizedString 本地化字符串。

另一個(gè)本地化過(guò)程中很有用的工具是 Auto Layout。例如,有在英文中很短的詞在德語(yǔ)中可能會(huì)很長(zhǎng)。如果根據(jù)英文單詞的長(zhǎng)度對(duì)視圖的尺寸做硬編碼,那么當(dāng)翻譯成德文的時(shí)候幾乎一定會(huì)遇上麻煩。通過(guò)使用 Auto Layout,讓標(biāo)簽控件自動(dòng)調(diào)整為內(nèi)容的尺寸,并向依賴元素添加一些其他的限制以確保重新設(shè)置尺寸,使這項(xiàng)工作變得非常簡(jiǎn)單。蘋(píng)果為此提供了一個(gè)很好的 介紹。另外,對(duì)于類似希伯來(lái)語(yǔ)這種順序從右到左的語(yǔ)言,如果你使用了 leading 和 trailing 屬性,整個(gè)視圖會(huì)自動(dòng)按照從右到左的順序展示,而不是硬編碼的從左至右。

測(cè)試

最后,讓我們考慮測(cè)試視圖的問(wèn)題。對(duì)于單元測(cè)試,你可以使用 Xcode 自帶的工具或者其它第三方框架。另外,可以使用 UIAutomation 或者其它基于它的工具。為此,你的視圖完全支持輔助功能是必要的。UIAutomation 并未充分得到利用的一個(gè)功能是截圖;你可以用它自動(dòng)對(duì)比視圖和設(shè)計(jì)以確保兩者每一個(gè)像素都分毫不差。(插一個(gè)無(wú)關(guān)的小提示:你還可以使用它來(lái)為應(yīng)用上架 App Store 自動(dòng)生成截圖,這在你有多個(gè)多國(guó)語(yǔ)言的應(yīng)用時(shí)會(huì)特別有用)。