UIView 触摸事件探究


UIView 触摸事件探究

最近遇到自定义的 UIView中,有不同的针对触摸事件的需求,针对 UIView 的响应触控事件,进行了一些了解.主要是针对 UIView 中hitTest:withEvent:函数的用法.

hitTest:withEvent:的官方说明文档是这样描述的,

Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.
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.

大意是说,hitTest:withEvent:这个函数会返回调用者视图层级中包含指定点的最远层级子 view(包括它自身),具体通过遍历每个子 view 的pointInside:withEvent:函数来判断该子 view 是否应该接收到此次交互事件,如果子 view 的pointInside:withEvent:返回 YES, 那么继续遍历该子 view的pointInside:withEvent:函数,直到发现包含这个点的最远层级子 view. 如果某个子 view 不包含这个点,那么它的整个 view 层级都会被忽略.

需要注意的是,hitTest:withEvent:会忽略那些被隐藏的,被 disable 的和透明度小于0.01的 view 对象.当处理触摸事件时,这个方法不会关心 view 的内容是否是透明的,也就是说,只要这个 view 自身不是被隐藏, disable, 透明度小于0.01,那么,即使这个点是处在这个 view 的透明部分中,这个 view也会被返回(透明部分的子 view 就会被忽略了).

另一个需要注意的是,落在消息接收者的边界之外的点永远不会被当做触摸事件传递下去,即使这个点确实是处于接收者某个子 view 中.当当前 view 的 clipsToBounds 属性设置为 NO, 并且某个子 view 超出当前 view 边界时会出现这种情况.这也就是我们接下来讲的hitTest:withEvent:第二种重要用法.



忽略事件响应
首先来看第一种情况,你不希望响应触摸事件,但又想要子 view 响应,具体如图1,


其中view C 是 View B 的子 view,View B 又是 View A 的子 view, 假设View A 是一个图片,需要响应用户的拖动事件, View B 是其中一个自定义控件,用来封装一个按钮 C(即 View C), 那么我们需要A 响应事件, C 响应事件,而不需要 B 响应事件,需要怎么做呢?

  • 首先,我们可以排除隐藏或者 disable B 的方法,因为这样, C 同样无法收到触摸事件,
  • 可以尝试重写touchesBegan:withEvent:,在 B 中重写该方法,如果 point 落在 C 中,则事件传递给 C, 如果落在 C 外部,则传递给 A 捕获此次操作.这样确实可以实现这个需求,但是有两个小问题:
    • 首先,假如 B 中不止 C 一个子 view, 那么我们就要进行多次判断,避免阻塞 B 中每个子 view 的事件捕获,这很笨重
    • 其次,因为 A 不在 B 的响应链中,在 B 的touchesBegan:withEvent:函数中,即使 B 排除了自身的子 view, 放弃此次捕获,但若想传给 A, 则,必须持有 A, 但 B 又是 A 的子 View, 这种方式容易产生死锁,且十分不优雅

故而,以上两种办法都不合适解决这种 case问题,最适合这种情况的,便是hitTest:withEvent:函数,使用hitTest:withEvent:解决这个问题的办法很简单, 在 B 中加入以下代码:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 查看本次事件本应该传递给哪个 view
  UIView* __tmpView = [super hitTest:point withEvent:event];
// 若传递给 view 自身,(在这里是 B),则返回 nil, 放弃此次事件捕获
  if (__tmpView == self) {
    return nil;
  }
  //否则,返回 view, 即 C
  return __tmpView;
}

这样的话,就不需要关心 B 到底有多少个子View了,也不需要关心如何把事件传递给 A, 因为 B 的hitTest:withEvent:返回如果是 nil 的话,系统会自动调用 B 的父 view 中的hitTest:withEvent:方法,即 A, 直到有 view 能够捕获此次事件.



超出边界的子 view事件捕获
hitTest:withEvent:的另外一种重要用法是用于保证超出边界的子view也能捕获到交互事件, 我们来看图2,

跟 case1一样, view 层级是 A->B->C, 不同的是, B 的clipsToBounds为 NO, 并且 C的边界超出了 B 的边界,正常情况下,由于hitTest:withEvent:不会将事件传递给超出父 view 边界的子 view, 这就导致了, C 永远接收不到触摸事件,重写B 的该方法能够很好地解决这个问题,

- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *tempView = [super hitTest:point withEvent:event];
        //加入 tempview 为 nil, 则代表落在 B 边界外部,
        if (!tempView) {
            //若点落在 C 中,则将事件传递给 C
            if (CGRectContainsPoint(C.frame, point)) {
                tempView = C;
            }
                // 否则,传递给需要处理事件的子view
            else{
            }
        }
        return tempView;
}

注意这段代码的 else 部分,假如 B 中不止 C 一个超出边界的子 view, 那么需要在此处进行判断,让其余的 view 也能正常响应事件.而不是每次都传递给 C.


总结

简单的说,hitTest:withEvent:方法是根据 view 层级来传递事件,而touchesBegan:withEvent:是根据响应链来传递,因此在view 层级中,针对不同子 view 对触摸事件的不同处理,可以很方便的使用hitTest:withEvent:函数来解决.而不是自定义的触摸全都通过覆盖touchesBegan:withEvent:函数. 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值