Friday, June 24, 2016

Receiving touch events outside bounds

Leave a Comment

There have been similar questions before and I have referred to Capturing touches on a subview outside the frame of its superview using hitTest:withEvent: and Delivering touch events to a view outside the bounds of its parent view. But they do not seem to answer my particular problem.

I have a custom control with the following structure:

+-------------------------------+ |           UIButton            | +-------------------------------+ |                          | |                          | |        UITableView       | |                          | |                          | +------------------------- + 

The custom control overrides the UIButton subclass and adds the UITableView as its subView. The idea is to have the whole control act like a dropdown. When the UIButton is pressed, the UITableView will dropdown and enable selection of a choice.

This all works fine with the hitTest overridden in UIButton as described in the Apple's Q&A link above, if the control is an immediate child of the topmost UIView. However, if the control is in another view hierarchy, the UITableView is not receiving touch events.

The following is the hitTest code used:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {     // Convert the point to the target view's coordinate system.     // The target view isn't necessarily the immediate subview     let pointForTargetView = self.spinnerTableView.convertPoint(point, fromView:self)       if (CGRectContainsPoint(self.spinnerTableView.bounds, pointForTargetView)) {         // The target view may have its view hierarchy,         // so call its hitTest method to return the right hit-test view         return self.spinnerTableView.hitTest(pointForTargetView, withEvent:event);     }     return super.hitTest(point, withEvent: event) } 

Edit:

I apologise for not having been able to look at the answers and check if they resolve the issue, due to some other tasks that require immediate attention. I shall check them and accept one that helps. Thanks for your patience.

5 Answers

Answers 1

As you stated:

However, if the control is in another view hierarchy, the UITableView is not receiving touch events.

Even if you made it working what if you have subviewed this control under another UIView??

This way we have a burden of converting points for the view hierarchy, my suggestion is that, as you can see in this control, it adds the tableview on the same hierarchy where the control itself is present. (e.g., it adds the table view on the controller where this control was added)

A part from the link:

-(void)textFieldDidBeginEditing:(UITextField *)textField {     [self setupSuggestionList];     [suggestionListView setHidden:NO];      // Add list to the super view.     if(self.dataSourceDelegate && [self.dataSourceDelegate isKindOfClass:UIViewController.class])     {         [((UIViewController *)self.dataSourceDelegate).view addSubview:suggestionListView];     }      // Setup list as per the given direction     [self adjustListFrameForDirection:dropDownDirection]; } 

Hope that helps!

Answers 2

I was thinking about the UIResponder , something like:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {         // Convert the point to the target view's coordinate system.         // The target view isn't necessarily the immediate subview          var responder: UIResponder = self         while responder.nextResponder() != nil {             responder = responder.nextResponder()!             if responder is UITableView {                 // Got UITableView                 break;             }         }         let pointForTargetView = responder.convertPoint(point, fromView:self)         if (CGRectContainsPoint(responder.bounds, pointForTargetView)) {             // The target view may have its view hierarchy,             // so call its hitTest method to return the right hit-test view             return self.responder.hitTest(pointForTargetView, withEvent:event);         }         return super.hitTest(point, withEvent: event) } 

If this method don't work, try to convertPoint with superview:

When you are sure that self.spinnerTableView is not nil and it is your table, do:

let pointForTargetView = self.spinnerTableView.superView.convertPoint(point, fromView:self)  

Answers 3

I think you should overridden pointInside implement:

class Control: UIButton {      var dropdown: Bool = false      override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {         if dropdown {             return CGRectContainsPoint(CGRect(x: 0, y:0, width: self.bounds.width, self.bounds.height + spinnerTableView.frame.height), point)         }         return super.pointInside(point, withEvent: event)     } } 

like this, superview call control's hittest when touch outside control bounds, it won't return nil.

But there are still a problem, if the control is in another view hierarchy, the UITableView may not receiving touch events.

I think this problem is normal,you can't decide other view's pointInside or not which under control, if the control superview hitTest return nil, the control hitTest method will not be called. Unless you overridden all view pointInside method which under the control, but it is unrealistic.

Answers 4

You need to subClass UIView,and use it as your UIViewController's root view.

class HitTestBottomView: UIView {    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {      if  let hitTestView  =    self.getHitTestButtonFrom(self){         let pointForHitTestView = hitTestView.convertPoint(point, fromView: self)         let pointForHitTestTableView = hitTestView.spinnerTableView.convertPoint(point, fromView: self)          if CGRectContainsPoint(hitTestView.bounds, pointForHitTestView) ||  CGRectContainsPoint(hitTestView.spinnerTableView.bounds, pointForHitTestTableView){             return hitTestView.hitTest(pointForHitTestView, withEvent: event)         }     }      return super.hitTest(point, withEvent: event)   }    func getHitTestButtonFrom(view:UIView) ->HitTestButton?{     if let testView = view as? HitTestButton {         return testView     }      for  subView  in view.subviews {         if let testView = self.getHitTestButtonFrom(subView) {             return testView         }     }     return nil   } } 

Answers 5

You have to implement

func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool 

for your UIButton and check UITableView bounds. Because hitTest first checks if this tap point is within your view's bounds then calls hitTest for your view. so you have to implement pointInside and return true for your bounds.

WWDC for advanced scroll views and touch handling techniques wwdc

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment