关键词
UITableView UITapGesture 点击空白收起键盘 触摸 点击 事件分发
描述
在当前页面可编辑状态下,点击空白处取消可编辑状态,收起键盘的功能非常常见。伴随着该功能,可能扩展出键盘出现,调整视图Frame, 可滑动,自动上移等一系列功能。
问题
UITableView中使用 UITapGesture冲突,表现奇怪
首先,实现点击空白处收起键盘功能,看起来蛮简单,只需要以下代码
UITapGestureRecognizer *hideKeyboardGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideKeyBoard:)];
[self.tableView addGestureRecognizer:hideKeyboardGesture];
- (void)hideKeyBoard:(UIGestureRecognizer *)gestureRecognizer
{
[self.tableView endEditing:YES];
}
那么问题来了
普通Cell(只包含Label,View,UIImageView)快速点击时,触发了Tap手势
普通Cell(只包含Label,View,UIImageView),稍微重一点点击时,触发了Tap手势,并且还触发了点击视觉效果,即setHighlighted方法,但没有触发didSelectRowAtIndexPath方法
普通Cell(只包含Label,View,UIImageView),长按,没有触发了Tap手势,触发了点击视觉效果,即setHighlighted方法,也触发了didSelectRowAtIndexPath方法
特殊Cell(包含UIButton,UITextField),不会触发Tap手势,也不会触发Cell点击效果
要解决以上问题,先明确一些背景知识
点击事件的分发:点击事件由UIApplication分发到当前触发点击事件的View,然后一直向上传递至最顶层的View,其中任何一环接受并处理了这个点击事件则停止,或者没有任何环节处理则直接静默不处理。至于UIApplication是如何分发到当前触发点击事件的View,是通过HitTest递归寻找到目标的。
具体参考转帖:http://blog.csdn.net/sakulafly/article/details/18766339
点击事件与TapGesture:Gesture Recognizers可能会延迟将触摸事件发送到hit-test view上,默认情况下,当Gesture Recognizers识别到手势后,会向hit-test view发送cancel消息,来取消之前发给hit-test view的事件
具体参考转帖:http://blog.csdn.net/chun799/article/details/8194893
请看苹果文档下面这段话
Interacting with Other User Interface Controls
In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer. This applies only to gesture recognition that overlaps the default action for a control, which includes:
A single finger single tap on a UIButton, UISwitch, UIStepper, UISegmentedControl, and UIPageControl.
A single finger swipe on the knob of a UISlider, in a direction parallel to the slider.
A single finger pan gesture on the knob of a UISwitch, in a direction parallel to the switch.
If you have a custom subclass of one of these controls and you want to change the default action, attach a gesture recognizer directly to the control instead of to the parent view. Then, the gesture recognizer receives the touch event first. As always, be sure to read the iOS Human Interface Guidelines to ensure that your app offers an intuitive user experience, especially when overriding the default behavior of a standard control.
分析
当给UITableView设置了UITapGesture以后,所有UITableView上的符合UITapGesture的点击事件都被UITapGestureRecognizer获取。解决了问题1
长按不符合UITapGesture的要求。解决了问题3
背景知识3,解决了问题4
对于问题2,可能是点击事件触发了UITapGestureRecognizer,但是cancel消息是延迟发出的,在消息送达前,Cell已经完成了点击事件的接收与处理,触发了setHighlighted方法设置了highlighted状态,但是cancel事件拦截了didSelectRowAtIndexPath
进一步分析,如何解决问题2的怪异现象
public var cancelsTouchesInView: Bool
// default is YES. causes touchesCancelled:withEvent: to be sent to the view for all touches recognized as part of this gesture immediately before the action method is called
public var delaysTouchesBegan: Bool
// default is NO. causes all touch events to be delivered to the target view only after this gesture has failed recognition. set to YES to prevent views from processing any touches that may be recognized as part of this gesture
public var delaysTouchesEnded: Bool
// default is YES. causes touchesEnded events to be delivered to the target view only after this gesture has failed recognition. this ensures that a touch that is part of the gesture can be cancelled if the gesture is recognized
cancelsTouchesInView属性表示是否发送touchesCancelled事件。
delaysTouchesBegan默认NO和delaysTouchesEnded默认YES正是问题2的根源,它导致cell接收到了TouchesBegan点击事件产生了点击效果,但是End事件却没有接收到从而无法调起didSelectRowAtIndexPath
delaysTouchesBegan设为Yes,则不会产生问题2这样奇怪的现象
但是如此解决问题,导致点击Cell只会触发UITapGestureRecognizer,无法点击Cell Item
解决方案
方法1:
// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application. Failure to
// do so is very likely to lead to incorrect behavior or crashes.
public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
public func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?)
方法2:
// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
@available(iOS 3.2, *)
optional public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool
方法3:
let bkgView = UIView(frame: UIScreen.mainScreen().bounds)
bkgView.backgroundColor = UIColor.clearColor()
bkgView.userInteractionEnabled = true
bkgView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "bkgOnClick:"))
tableView.insertSubview(bkgView, atIndex: 0)
以上是笔者想到的方法,欢迎拍砖,如果朋友有什么更好更简便的方法,请评论告诉我,谢谢