iOS - Target-Action機制建立自己的UI控制項需要瞭解的知識

來源:互聯網
上載者:User

標籤:開發   pad   轉寄   圖片   document   預先處理   調用   gpo   思考   

我們在開發應用的時候,經常會用到各種各樣的控制項,諸如按鈕(UIButton)、滑塊(UISlider)、分頁控制項(UIPageControl)等。這些控制項用來與使用者進行互動,響應使用者的操作。我們查看這些類的繼承體系,可以看到它們都是繼承於UIControl類。UIControl是控制項類的基類,它是一個抽象基類,我們不能直接使用UIControl類來執行個體化控制項,它只是為控制項子類定義一些通用的介面,並提供一些基礎實現,以在事件發生時,預先處理這些訊息並將它們發送到指定目標對象上。

本文將通過一個自訂的UIControl子類來看看UIControl的基本使用方法。不過在開始之前,讓我們先來瞭解一下Target-Action機制。

Target-Action機制

Target-action是一種設計模式,直譯過來就是”目標-行為”。當我們通過代碼為一個按鈕添加一個點擊事件時,通常是如下處理:

1 [button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];

也就是說,當按鈕的點擊事件發生時,會將訊息發送到target(此處即為self對象),並由target對象的tapButton:方法來處理相應的事件。其基本過程可以用來描述:

註:圖片來源於官方文檔Cocoa Application Competencies for iOS – Target Action

即當事件發生時,事件會被發送到控制項對象中,然後再由這個控制項對象去觸發target對象上的action行為,來最終處理事件。因此,Target-Action機制由兩部分組成:即目標對象和行為Selector。目標對象指定最終處理事件的對象,而行為Selector則是處理事件的方法。

有關Target-Action機制的具體描述,大家可以參考Cocoa Application Competencies for iOS – Target Action。我們將會在下面討論一些Target-action更深入的東西。

執行個體:一個帶Label的圖片控制項

回到我們的正題來,我們將實現一個帶Label的圖片控制項。通常情況下,我們會基於以下兩個原因來實現一個自訂的控制項:

  • 對於特定的事件,我們需要觀察或修改分發到target對象的行為訊息。

  • 提供自訂的跟蹤行為。

本例將會簡單地結合這兩者。先來看看效果:

這個控制項很簡單,以圖片為背景,然後在下方顯示一個Label。

先建立UIControl的一個子類,我們需要傳入一個字串和一個UIImage對象:

12345 @interface ImageControl : UIControl - (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title image:(UIImage *)image; @end

基礎的布局我們在此不討論。我們先來看看UIControl為我們提供了哪些自訂跟蹤行為的方法。

跟蹤觸摸事件

如果是想提供自訂的跟蹤行為,則可以重寫以下幾個方法:

1234 - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event- (void)cancelTrackingWithEvent:(UIEvent *)event

這四個方法分別對應的時跟蹤開始、移動、結束、取消四種狀態。看起來是不是很熟悉?這跟UIResponse提供的四個事件跟蹤方法是不是挺像的?我們來看看UIResponse的四個方法:

1234 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

我們可以看到,上面兩組方法的參數基本相同,只不過UIControl的是針對單點觸摸,而UIResponse可能是多點觸摸。另外,傳回值也是大同小異。由於UIControl本身是視圖,所以它實際上也繼承了UIResponse的這四個方法。如果測試一下,我們會發現在針對控制項的觸摸事件發生時,這兩組方法都會被調用,而且互不干涉。

為了判斷當前對象是否正在追蹤觸摸操作,UIControl定義了一個tracking屬性。該值如果為YES,則表明正在追蹤。這對於我們是更加方便了,不需要自己再去額外定義一個變數來做處理。

在測試中,我們可以發現當我們的觸摸點沿著螢幕移出控制項地區名,還是會繼續追蹤觸摸操作,cancelTrackingWithEvent:訊息並未被發送。為了判斷當前觸摸點是否在控制項地區類,可以使用touchInside屬性,這是個唯讀屬性。不過實測的結果是,在控制項地區周邊一定範圍內,該值還是會被標記為YES,即用於判定touchInside為YES的地區會比控制項地區要大。

觀察或修改分發到target對象的行為訊息

對於一個給定的事件,UIControl會調用sendAction:to:forEvent:來將行為訊息轉寄到UIApplication對象,再由UIApplication對象調用其sendAction:to:fromSender:forEvent:方法來將訊息分發到指定的target上,而如果我們沒有指定target,則會將事件分發到響應鏈上第一個想處理訊息的對象上。而如果子類想監控或修改這種行為的話,則可以重寫這個方法。

在我們的執行個體中,做了個小小的處理,將外部添加的Target-Action放在控制項內部來處理事件,因此,我們的代碼實現如下:

123456789101112131415161718192021222324252627 // ImageControl.m- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {  // 將事件傳遞到對象本身來處理    [super sendAction:@selector(handleAction:) to:self forEvent:event];} - (void)handleAction:(id)sender {     NSLog(@"handle Action");} // ViewController.m - (void)viewDidLoad {    [super viewDidLoad];     self.view.backgroundColor = [UIColor whiteColor];     ImageControl *control = [[ImageControl alloc] initWithFrame:(CGRect){50.0f, 100.0f, 200.0f, 300.0f} title:@"This is a demo" image:[UIImage imageNamed:@"demo"]];    // ...     [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside];}- (void)tapImageControl:(id)sender {     NSLog(@"sender = %@", sender);}

由於我們重寫了sendAction:to:forEvent:方法,所以最後處理事件的SelectorImageControlhandleAction:方法,而不是ViewController的tapImageControl:方法。

另外,sendAction:to:forEvent:實際上也被UIControl的另一個方法所調用,即sendActionsForControlEvents:。這個方法的作用是發送與指定類型相關的所有行為訊息。我們可以在任意位置(包括控制項內部和外部)調用控制項的這個方法來發送參數controlEvents指定的訊息。在我們的樣本中,在ViewController.m中作了如下測試:

123456 - (void)viewDidLoad {    // ...    [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside];     [control sendActionsForControlEvents:UIControlEventTouchUpInside];}

可以看到在未點擊控制項的情況下,觸發了UIControlEventTouchUpInside事件,並列印了handle Action日誌。

Target-Action的管理

為一個控制項對象添加、刪除Target-Action的操作我們都已經很熟悉了,主要使用的是以下兩個方法:

1234 // 添加- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents - (void)removeTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents

如果想擷取控制項對象所有相關的target對象,則可以調用allTargets方法,該方法返回一個集合。集合中可能包含NSNull對象,表示至少有一個nil目標對象。

而如果想擷取某個target對象及事件相關的所有action,則可以調用actionsForTarget:forControlEvent:方法。

不過,這些都是UIControl開放出來的介面。我們還是想要探究一下,UIControl是如何去管理Target-Action的呢?

實際上,我們在程式某個合適的位置打個斷點來觀察UIControl的內部結構,可以看到這樣的結果:

因此,UIControl內部實際上是有一個可變數組(_targetActions)來儲存Target-Action,數組中的每個元素是一個UIControlTargetAction對象。UIControlTargetAction類是一個私人類,我們可以在iOS-Runtime-Header中找到它的標頭檔:

1234567891011121314 @interface UIControlTargetAction : NSObject {    SEL _action;    BOOL _cancelled;    unsigned int _eventMask;    id _target;} @property (nonatomic) BOOL cancelled; - (void).cxx_destruct;- (BOOL)cancelled;- (void)setCancelled:(BOOL)arg1; @end

可以看到UIControlTargetAction對象維護了一個Target-Action所必須的三要素,即targetaction及對應的事件eventMask

如果仔細想想,會發現一個有意思的問題。我們來看看執行個體中ViewController(target)與ImageControl執行個體(control)的參考關聯性,如所示:

嗯,循環參考。

既然這樣,就必須想辦法打破這種循環參考。那麼在這5個環節中,哪個地方最適合做這件事呢?仔細思考一樣,1、2、4肯定是不行的,3也不太合適,那就只有5了。在上面的UIControlTargetAction標頭檔中,並沒有辦法看出_target是以weak方式聲明的,那有證據嗎?

我們在工程中打個Symbolic斷點,如下所示:

運行程式,程式會進入[UIControl addTarget:action:forControlEvents:]方法的彙編字碼頁,在這裡,我們可以找到一些蛛絲馬跡。如所示:

可以看到,對於_target成員變數,在UIControlTargetAction的初始化方法中調用了objc_storeWeak,即這個成員變數對外部傳進來的target對象是以weak的方式引用的。

其實在UIControl的文檔中,addTarget:action:forControlEvents:方法的說明還有這麼一句:

When you call this method, target is not retained.

另外,如果我們以同一組target-action和event多次調用addTarget:action:forControlEvents:方法,在_targetActions中並不會重複添加UIControlTargetAction對象。

小結

控制項是我們在開發中常用的視圖工具,能很好的表達使用者的意圖。我們可以使用UIKit提供的控制項,也可以自訂控制項。當然,UIControl除了上述的一些方法,還有一些屬性和方法,以及一些常量,大家可以參考文檔。

iOS - Target-Action機制建立自己的UI控制項需要瞭解的知識

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.