iOS自定義控件教程(四)UIControl - 幕後的英雄

上一篇文章咱們介紹了UIView的觸摸事件響應和簡單動畫,可是並無將觸摸事件封裝。咱們今天介紹Demo中最後一部分 —— 輸出響應事件。git

Github下載源碼github

我麼知道Objective-C是採用消息機制(messaging)調用方法的,例如咱們調用UIViewinit方法設計模式

UIView * simpleView = [[UIView alloc] init];

簡單的描述一下其中的過程:app

  1. 程序一運行,全部的類方法(‘+’開頭)和實例方法(‘-’開頭)的接口內存地址都被寫入一張hash表中框架

  2. 咱們向UIView發送類方法alloc消息,runtime(運行時環境)根據前面說的hash表,查找對應類(UIView)的對應類方法(alloc)的內存地址,而後調用ide

  3. 若是UIView並未實現alloc方法,runtime會轉而查找UIView的父類是否實現了alloc方法,若是實現了,就將消息投遞給父類的alloc方法;若是沒有實現,轉而查找UIView父類的父類是否實現,重複這一過程直到將消息投遞出去工具

  4. 若是最終發現投遞不出去,則會拋出一個最多見的異常unrecognized selector sent to instance + 內存地址,也就是你調用了一個沒有實現的方法動畫

不過,咱們今天遇到的問題單單依靠消息機制並不能很好的解決。atom

需求 咱們須要將DemoXXXSegmentView獲取的觸摸事件,反饋給當前的UIViewContoller,應該怎麼作?這個問題就是所謂的反向傳值問題。spa

1. 直接調用

咱們從最蠢的作法提及,雖然是蠢,可是是可行的,不過不要模仿啊,單純爲了講原理和做對照

@interface ViewController ()
@property (strong, nonatomic) XXXSegmentView *segmentView;
- (void)segmentDidSelectIdx:(NSInteger)idx;
@end
@interface XXXSegmentView : UIView
@property (weak, nonatomic) ViewController *viewController;
@end

咱們在給XXXSegmentView加上一個viewController屬性,而後就能夠在獲取觸摸事件時,經過調用ViewControllersegmentDidSelectIdx方法,傳遞選擇標籤這個事件。

這樣是可行的,可是缺點十分明顯:耦合性過高,XXXSegmentView須要引用ViewController頭文件,不符合低耦合這個基本原則。

2. delegate(委託)模式

objc的delegate設計模式,能夠解決上面的問題。但根據objc的設計初衷,這個問題用delegate解決真的有種殺雞用牛刀的感受。

@interface XXXSegmentView : UIView
@property (nonatomic, weak) id delegate;
@end

這裏的delegate屬性,是一個id類型的屬性,id這個類型就是objc的動態類型,編譯器不關心它是什麼類型,因此id類型的對象,能夠調用全部聲明過的類方法和實例方法,而編譯器不會報錯。

這樣咱們就能夠個把viewController做爲XXXSegmentViewdelegate屬性傳入,XXXSegmentView無需知道本身的delegate是什麼類,即可以直接調用delegate的實例方法。

if (self.delegate && [self.delegate respondsToSelector:@selector(segmentDidSelectIdx:)]) {
        [self.delegate segmentDidSelectIdx:idx];
}

注意咱們在調用以前,首先檢查selfdelegate是否賦值,而後經過respondsToSelector確認delegate實現了segmentDidSelectIdx方法,最後才傳遞消息。這兩步十分重要,delegate做爲動態類型,編譯器編譯階段是沒法發現問題的,因此運行時要進行確認。

注: 標準的委託模式是要結合協議(Protocol)一塊兒使用的,這裏就很少講了。

3. Target模式

這要從一個類提及,他叫UIControl

UIControlUIView的子類,是UIKit框架中可交互的控件的基類,通常不直接使用。咱們用的比較多的例如UIButtonUISwitchUITextField等都是他的子類。

UIControliOS的人機交互制定了一系列的標準:

例如最多見的UIControlEvents枚舉,定義了iOS交互中的交互方式

typedef NS_OPTIONS(NSUInteger, UIControlEvents) {
    UIControlEventTouchDown                                         = 1 <<  0,      // on all touch downs
    UIControlEventTouchDownRepeat                                   = 1 <<  1,      // on multiple touchdowns (tap count > 1)
    UIControlEventTouchDragInside                                   = 1 <<  2,
    UIControlEventTouchDragOutside                                  = 1 <<  3,
    UIControlEventTouchDragEnter                                    = 1 <<  4,
    UIControlEventTouchDragExit                                     = 1 <<  5,
    UIControlEventTouchUpInside                                     = 1 <<  6,
    UIControlEventTouchUpOutside                                    = 1 <<  7,
    UIControlEventTouchCancel                                       = 1 <<  8,

    UIControlEventValueChanged                                      = 1 << 12,     // sliders, etc.
    UIControlEventPrimaryActionTriggered NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 13,     // semantic action: for buttons, etc.

    UIControlEventEditingDidBegin                                   = 1 << 16,     // UITextField
    UIControlEventEditingChanged                                    = 1 << 17,
    UIControlEventEditingDidEnd                                     = 1 << 18,
    UIControlEventEditingDidEndOnExit                               = 1 << 19,     // 'return key' ending editing

    UIControlEventAllTouchEvents                                    = 0x00000FFF,  // for touch events
    UIControlEventAllEditingEvents                                  = 0x000F0000,  // for UITextField
    UIControlEventApplicationReserved                               = 0x0F000000,  // range available for application use
    UIControlEventSystemReserved                                    = 0xF0000000,  // range reserved for internal framework use
    UIControlEventAllEvents                                         = 0xFFFFFFFF
};

又例如UIControlState定義了控件的基本狀態

typedef NS_OPTIONS(NSUInteger, UIControlState) {
    UIControlStateNormal       = 0,
    UIControlStateHighlighted  = 1 << 0,                  // used when UIControl isHighlighted is set
    UIControlStateDisabled     = 1 << 1,
    UIControlStateSelected     = 1 << 2,                  // flag usable by app (see below)
    UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
    UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
    UIControlStateReserved     = 0xFF000000               // flags reserved for internal framework use
};

同時提供了給控件反饋交互操做的一系列方法,例如咱們今天要講的

- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;

好比咱們有一個按鈕,當他點擊時候,咱們執行ViewContollr的-(void)click:(id)sender方法,能夠這麼寫:

UIButton* button = [UIButton buttonWithType:UIButtonTypeSystem];
[button addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];

這裏傳入的UIControlEventTouchUpInside枚舉量,就是在控件frame內按下,而後擡起這樣一個事件,UIContol將這個事件做爲key,和目標(target)和目標方法(action)存到了本身私有的字典裏。當用戶點擊按鈕時,UIControl響應了觸摸鏈的touchesEnded方法,便會根據私有字典,把對應UIControlEventTouchUpInside的目標(target)和目標方法(action)調用,這樣完成事件的回傳。

這是一個很好的設計,從原則上講,咱們的XXXSegmentView是一個可交互控件,理應繼承於UIControl而非UIView,但筆者偷懶了,讀者有興趣能夠本身嘗試改寫。

4. block(塊語法)

沒有繼承UIControl,筆者只好祭出終極大殺器,block。block語法特性加入iOS已經有段日子了,由於使用方法篇幅太大,這裏就不細說了,推薦一篇相關教程

咱們知道block是能夠看成對象看待的,因此給XXXSegmentView添加下面這個屬性

@property (nonatomic, strong) void (^ didSelectBlock)(NSUInteger idx);

ViewContoller中,咱們給XXXSegmentViewdidSelectBlock賦值

@property (weak, nonatomic) IBOutlet XXXSegmentView *segment;

[segment setDidSelectBlock:^(NSUInteger idx) {
        NSLog(@"segment select %@",@(idx));
}];

而後在XXXSegmentView中加入block調用

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    
    //.....其餘代碼
    
    if (self.didSelectBlock) {
        self.didSelectBlock(touchNumber);
    }
}

block的調用方法相似C語言的方法調用,傳參格式也相同,注意使用前也要進行非空檢測哦。

不推薦的方法

有兩種反向傳值的方法我並不推薦,一種是單例,一種是通知

單例做爲一種特殊的設計結構,適合存儲全局變量,拿來做爲單條響應鏈的存儲媒介,有些大材小用;通知做爲另外一種神奇的工具,蘋果的工程師徹底是將其用來深度封裝框架所用,本身寫代碼使用通知,調理不清晰,結構不明朗,不說換我的,本身隔段時間可能就忘了本身在哪裏接收了通知。因此能不用盡可能不要用。

小結

最終效果

至此,咱們自制UIKit控件的第一篇教程就結束了,感興趣的朋友能夠從Github下載源碼對照分析。這幾篇教程主要針對一些有objc基礎,但UIKit剛入門的初學者,但願能幫到大家。

最後跟你們分享一個最的最新做品:zsy78191/XXXRoundMenuButton

圖片描述

相關文章
相關標籤/搜索