我們都知道,通過設(shè)置視圖View的clipToBounds=NO屬性能夠允許子View超過父View, 但是超出父View的部分不會響應(yīng)手勢事件。
之所以產(chǎn)生了這篇文章,是因為在實際的開發(fā)過程中,為了配合UI設(shè)計還有一些用戶體驗,我們需要讓子View在超出父控件后,仍然可以響應(yīng)點擊事件。
一個常見的場景是Tab欄,比如:

6CE0EB4C-FB25-4CD0-B43D-8CDEF7F3573C.png
另一個場景就是地圖應(yīng)用的點標(biāo),使用過百度地圖和高度地圖的SDK,我發(fā)現(xiàn)點標(biāo)的選中事件都坐落在圖標(biāo)上,不管將點標(biāo)VIEW的bounds設(shè)置多大都無濟(jì)于事。這就很尷尬了。因為我們有可能還需要在圖標(biāo)的上方或下方加入標(biāo)題文字,而這個文字很可能超出了整個點標(biāo)控件,并且由于用戶習(xí)慣會去點擊文字,因此我們需要讓用戶點擊文字時也觸發(fā)點標(biāo)的選中事件。

32290F43-2C08-4D75-82CC-B66EF23B0585.png
諸如此類應(yīng)用場景中,我們需要重寫View的手勢響應(yīng)方法,以地圖點標(biāo)為例:
// 地圖標(biāo)點視圖 AnnotationView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 如果View不為空,代表觸摸的位置有視圖攔截了這個事件
UIView *view = [super hitTest:point withEvent:event];
if (view == nil) {
for (UIView *subView in self.subviews) {
CGPoint myPoint = [subView convertPoint:point fromView:self];
// 判斷觸摸的位置是否存在自己的子控件,即便這個子控件超出了父視圖的范圍
if (CGRectContainsPoint(subView.bounds, myPoint)) {
// 返回一個攔截事件的對象,這個對象可以是self,也可以是具體的子控件
return self;
}
}
}
return view;
}
用戶點擊屏幕時,系統(tǒng)會將事件分發(fā)給所有可見視圖的hitTest方法,如果該方法返回的是nil,則表示該視圖不處理手勢事件,反之則捕獲該事件。
當(dāng)方法返回nil的時候,我們可以追加判斷,是否‘點在了超出容器范圍的子控件上’,如果是則攔截事件。