官方描述
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Returns a Boolean value indicating whether the receiver contains the specified point.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
The view object that is the farthest descendent the current view and contains point. Returns nil if the point lies completely outside the receiver’s view hierarchy.
This method traverses the view hierarchy by calling the pointInside:withEvent: method of each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is similarly traversed until the frontmost view containing the specified point is found. If a view does not contain the point, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.
Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to NO and the affected subview extends beyond the view’s bounds.
事件處理流程
1 當用戶點擊屏幕時,會產生一個觸摸事件,系統會將該事件加入到一個由UIApplication管理的事件隊列中
2 UIApplication會從事件隊列中取出最前面的事件進行分發以便處理,通常,先發送事件給應用程序的主窗口(UIWindow)
3 主窗口會調用hitTest:withEvent:方法在視圖(UIView)層次結構中找到一個最合適的UIView來處理觸摸事件
(hitTest:withEvent:其實是UIView的一個方法,UIWindow繼承自UIView,因此主窗口UIWindow也是屬于視圖的一種)
hitTest:withEvent:方法大致處理流程是這樣的:
一 首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內:
若pointInside:withEvent:方法返回NO,說明觸摸點不在當前視圖內,則當前視圖的hitTest:withEvent:返回nil
若pointInside:withEvent:方法返回YES,說明觸摸點在當前視圖內,則遍歷當前視圖的所有子視圖(subviews),調用子視圖的hitTest:withEvent:方法重復前面的步驟,子視圖的遍歷順序是從top到bottom,即從subviews數組的末尾向前遍歷,直到有子視圖的hitTest:withEvent:方法返回非空對象或者全部子視圖遍歷完畢:
二
- 若第一次有子視圖的hitTest:withEvent:方法返回非空對象,則當前視圖的hitTest:withEvent:方法就返回此對象,處理結束
- 若所有子視圖的hitTest:withEvent:方法都返回nil,則當前視圖的hitTest:withEvent:方法返回當前視圖自身(self)
4 最終,這個觸摸事件交給主窗口的hitTest:withEvent:方法返回的視圖對象去處理
應用場景
1 擴大Button的點擊區域(上下左右各增加20)
重寫自定義Button的pointInside方法
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(CGRectInset(self.bounds, -20, -20), point)) {
return YES;
}
return NO;
}
2 子view超出了父view的bounds響應事件
正常情況下,子View超出父View的bounds的那一部分是不會響應事件的
解決方法:重寫父View
的pointInside方法
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL flag = NO;
for (UIView *view in self.subviews) {
if (CGRectContainsPoint(view.frame, point)){
flag = YES;
break;
}
}
return flag;
}
3 如果一個Button被一個View蓋住了,在觸摸View時,希望該Button能夠響應事件
解決方法1
點擊View及View的非交互子View(例如UIImageView),則該Button可以響應事件
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL next = YES;
for (UIView *view in self.subviews) {
if ([view isKindOfClass:[UIControl class]]) {
if (CGRectContainsPoint(view.frame, point)){
next = NO;
break;
}
}
}
return !next;
}
解決方法2
點擊View本身Button會響應該事件,點擊View的任何一個子View,Button不會響應事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if (view == self) {
return nil;
}
return view;
}
4 特殊的UIScrollView
如圖所示,該ScrollView可以顯示上一頁和下一頁的部分界面,紅色框是ScrollView的frame,綠色框部分是設置了clipsToBounds = NO的結果,但是正如情況2提到的,超出部分是不響應事件的。
簡單的代碼實現
CGSize size = [UIScreen mainScreen].bounds.size;
CGFloat width = size.width - 80;
JCScrollView *scrollView = [[JCScrollView alloc] initWithFrame:CGRectMake(40, size.height - 150 - 30, width, 150)];
scrollView.pagingEnabled = YES;
scrollView.clipsToBounds = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO;
[scrollView setContentSize:CGSizeMake(width * 5, 150)];
for (NSInteger i = 0; i < 5; i++) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(5 + width * i, 0, width - 10, 150)];
view.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255) / 255.0f green:arc4random_uniform(255) / 255.0f blue:arc4random_uniform(255) / 255.0f alpha:1];
[scrollView addSubview:view];
}
[self.view addSubview:scrollView];
如果需要綠色框響應ScrollView的滾動事件,則原理和情況1一樣
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(CGRectInset(self.bounds, -40, 0), point)) {
return YES;
}
return NO;
}