Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Receiving touch events outside bounds

Tags:

ios

uikit

swift

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.

like image 962
Rajesh Avatar asked Jun 15 '16 16:06

Rajesh


3 Answers

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!

like image 87
NeverHopeless Avatar answered Oct 24 '22 02:10

NeverHopeless


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.

like image 34
maquannene Avatar answered Oct 24 '22 02:10

maquannene


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) 
like image 31
Alessandro Ornano Avatar answered Oct 24 '22 02:10

Alessandro Ornano