上一篇文章咱們介紹了UIView的觸摸事件響應和簡單動畫,可是並無將觸摸事件封裝。咱們今天介紹Demo中最後一部分 —— 輸出響應事件。git
Github下載源碼github
我麼知道Objective-C
是採用消息機制
(messaging)調用方法的,例如咱們調用UIView
的init
方法設計模式
UIView * simpleView = [[UIView alloc] init];
簡單的描述一下其中的過程:app
程序一運行,全部的類方法(‘+’開頭)和實例方法(‘-’開頭)的接口內存地址都被寫入一張hash表中框架
咱們向UIView
發送類方法alloc
消息,runtime(運行時環境)根據前面說的hash表,查找對應類(UIView)的對應類方法(alloc)的內存地址,而後調用ide
若是UIView並未實現alloc方法,runtime會轉而查找UIView的父類是否實現了alloc方法,若是實現了,就將消息投遞給父類的alloc方法;若是沒有實現,轉而查找UIView父類的父類是否實現,重複這一過程直到將消息投遞出去工具
若是最終發現投遞不出去,則會拋出一個最多見的異常unrecognized selector sent to instance + 內存地址
,也就是你調用了一個沒有實現的方法動畫
不過,咱們今天遇到的問題單單依靠消息機制
並不能很好的解決。atom
需求 咱們須要將Demo
中XXXSegmentView
獲取的觸摸事件,反饋給當前的UIViewContoller,應該怎麼作?這個問題就是所謂的反向傳值
問題。spa
咱們從最蠢的作法提及,雖然是蠢,可是是可行的,不過不要模仿啊,單純爲了講原理和做對照
@interface ViewController () @property (strong, nonatomic) XXXSegmentView *segmentView; - (void)segmentDidSelectIdx:(NSInteger)idx; @end
@interface XXXSegmentView : UIView @property (weak, nonatomic) ViewController *viewController; @end
咱們在給XXXSegmentView
加上一個viewController
屬性,而後就能夠在獲取觸摸事件時,經過調用ViewController
的segmentDidSelectIdx
方法,傳遞選擇標籤這個事件。
這樣是可行的,可是缺點十分明顯:耦合性過高,XXXSegmentView
須要引用ViewController
頭文件,不符合低耦合這個基本原則。
objc
的delegate設計模式,能夠解決上面的問題。但根據objc
的設計初衷,這個問題用delegate解決真的有種殺雞用牛刀的感受。
@interface XXXSegmentView : UIView @property (nonatomic, weak) id delegate; @end
這裏的delegate屬性,是一個id
類型的屬性,id
這個類型就是objc
的動態類型,編譯器不關心它是什麼類型,因此id
類型的對象,能夠調用全部聲明過的類方法和實例方法,而編譯器不會報錯。
這樣咱們就能夠個把viewController
做爲XXXSegmentView
的delegate
屬性傳入,XXXSegmentView
無需知道本身的delegate
是什麼類,即可以直接調用delegate
的實例方法。
if (self.delegate && [self.delegate respondsToSelector:@selector(segmentDidSelectIdx:)]) { [self.delegate segmentDidSelectIdx:idx]; }
注意咱們在調用以前,首先檢查self
的delegate
是否賦值,而後經過respondsToSelector
確認delegate
實現了segmentDidSelectIdx
方法,最後才傳遞消息。這兩步十分重要,delegate
做爲動態類型,編譯器編譯階段是沒法發現問題的,因此運行時要進行確認。
注: 標準的委託模式是要結合協議(Protocol)一塊兒使用的,這裏就很少講了。
這要從一個類提及,他叫UIControl
。
UIControl
是UIView
的子類,是UIKit
框架中可交互的控件的基類,通常不直接使用。咱們用的比較多的例如UIButton
、UISwitch
、UITextField
等都是他的子類。
UIControl
爲iOS
的人機交互制定了一系列的標準:
例如最多見的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
,但筆者偷懶了,讀者有興趣能夠本身嘗試改寫。
沒有繼承UIControl
,筆者只好祭出終極大殺器,block
。block語法特性加入iOS已經有段日子了,由於使用方法篇幅太大,這裏就不細說了,推薦一篇相關教程。
咱們知道block是能夠看成對象看待的,因此給XXXSegmentView
添加下面這個屬性
@property (nonatomic, strong) void (^ didSelectBlock)(NSUInteger idx);
在ViewContoller
中,咱們給XXXSegmentView
的didSelectBlock
賦值
@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