上一次咱們一塊兒作一個多段選擇的自定義控件,順便學習UIView的基本屬性和方法。在iOS自定義控件教程(一)中咱們完成了UILabel佈局的工做,接下來咱們一塊兒研究一下觸摸響應鏈原理。
最終實現的效果:Github下載源碼git
先簡單普及一下響應鏈原理,咱們能夠簡單地認爲iPhone屏幕就是一個容器,咱們看到的各類控件(UIView和UIView子類)都是屏幕(UIWindow)這個容器中的子容器,最外層的容器是應用委託(AppDelegate)的屬性keyWindow,其實UIWindow也是UIView的子類。github
這些容器的相互關係,就是咱們最先學數據結構接觸的多叉樹關係,keyWindow就是這棵樹的Root,其它它的子View都是分支。例如上面的例子,咱們用xcode進行調試能夠獲得下圖。注意在調試過程當中,纔有這排功能:xcode
獲得下面的層級結構數據結構
在咱們的Demo裏面,總共有兩個XXXSegmentView
,第一個XXXSegmentView
有四個子UILabel
,而他的父View
是當前ViewContoller
的主View
,一個背景是純白色的全屏View
。佈局
當觸摸事件被iPhone硬件接收到時,一個鏈式的觸摸信號就被開啓了。最早接收到觸摸事件的是Root
,也就是咱們應用程序的keyWindow
,keyWindow
再將觸摸事件傳遞給它的一級子View們。這個傳遞過程不須要開發者用代碼實現,若是開發者有須要重寫傳遞,須要使用的是UIView
的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
方法。學習
這個方法返回的UIView,就是父View決定讓哪一個孩子做爲這個觸摸的響應View。這樣,觸摸事件就不斷往下級傳遞,對應的View才能根據觸摸事件改變樣式。spa
新人在開發時,常常會遇到不響應觸摸的狀況。有兩種常見的可能打斷觸摸鏈調試
1. 目標View到Root的分支中,有View沒有開啓觸摸事件,打斷了響應鏈傳遞。code
這裏須要知道UIView有一個基本屬性叫作userInteractionEnabled
,這個屬性爲YES
時才能響應觸摸事件。因此檢查一下分支中是否有View的觸摸開關被關閉。例如,UIImageView
這個展現圖片的類,默認就是不響應觸摸鏈的,若是你須要繼承它寫一個新View,而且須要觸摸,或者給他添加了須要觸摸的子View,請修改他的userInteractionEnabled
屬性。繼承
2. 目標View的觸摸事件,由於層級關係被上層覆蓋的View劫走,哪怕這個View多是透明的
根據鏈式響應原理,父View會將觸摸鏈傳給符合相應條件,而且層級關係最上層的View,因此下層的View接收不到觸摸事件,遇到這種狀況,你能夠根據須要進行處理。若是覆蓋在上層View層級順序有誤,經過調用他們父View的兩個方法能夠輕鬆交換他們層級覆蓋關係。
- (void)bringSubviewToFront:(UIView *)view; - (void)sendSubviewToBack:(UIView *)view;
若是上層View就是要放在上面,那就關掉上層View的響應鏈,userInteractionEnabled
設爲NO
,這樣父View在進行hitTest時就會拋棄這個View,選擇層級更低的View傳遞。
這裏請注意UIView
的幾個不一樣的屬性。
backgroundColor 背景顏色,UIColor類
alpha 顏色中的alpha通道值,這個值範圍0-1,等於0時徹底透明,等於1時,徹底不透明
hidden BOOL型,是否隱藏
這三個屬性,均可以實現UIView隱身的效果。例如一個空的UIView,backgroundColor
設爲[UIColor clearColor]時,背景色是透明的;alpha設爲0時,UIView也是透明的;hidden設爲YES時,UIView一樣不可見。
但他們的區別也很明顯,backgroundColor
屬性的修改,不影響子View,因此子View不會由於父View的backgroundColor
設爲[UIColor clearColor]而隱藏,同時,父View一樣響應觸摸鏈。
而alpha值則不一樣,渲染時alpha值時一個疊加屬性,例如父View透明度爲0.5,子View透明度爲0.5,這時渲染出來的真實效果,子View的透明的應該爲0.5*0.5 = 0.25,因此當父View的alpha爲0時,子View也是徹底不可見的。另外,alpha爲0的View,是不響應觸摸鏈的。
最後這個hidden屬性,直接從根源,決定要不要渲染這個View,不像alpha屬性,是不須要渲染時計算最終的渲染效果的,由於這個屬性爲YES
時,根本不進行渲染,因此渲染鏈都斷了,子View也不會渲染了,更不會響應觸摸了。
響應鏈hitTest的三個條件,就是userInteractionEnabled = YES,而且alpha != 0, 且hidden = NO。
樹型結構,既是UIView
的觸摸響應結構,也是渲染鏈結構,觸摸鏈經過下面四個方法傳遞,UIView
是UIResponder
的子類,下面四個方法是觸摸鏈傳遞的會調用的方法。分別對應開始
,移動
,結束
,取消
四種狀態。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
渲染樹型結構要更加簡單一點,調用的是- (void)setNeedsDisplay;
方法,也就是在父View的setNeedsDisplay方法中,調用子View的setNeedsDisplay方法,這個方法會通知View在下一個cpu時間裏,進行從新渲染,也就是調用他們的- (void)drawRect:(CGRect)rect;
方法實現繪製。
可是自從手勢(UIGesture)加入以後,觸摸鏈開發用的愈來愈少,但咱們的Demo仍是使用了簡單的觸摸鏈。後面的章節咱們將介紹功能更爲便捷和強大手勢(UIGesture)功能。