Search code examples
iosswiftuiviewtouchhierarchy

Touch event does not register outside of a child UIView that partially extends outside of parent UIView


I have a parent view which contains two elements. Essentially, they comprise a dropdown select. Referencing the below image: When the blue element is clicked, it shows an (initially hidden) dropdown UITableView. This UITableView is partially inside of the same parent view that also contains the blue element.

enter image description here

When I try to click on one of the UITableViewCells, only the first cell registers a touch event. If the table view is situated such that the first cell is partly inside of the parent, only clicks on the half of the image that is inside of the parent register.

This seems to be a hierarchy issue. I have tried:

  1. Adjusting Z-indices
  2. Situating the entire UITableView outside of the parent in the Storyboard hierarchy, but visually positioning it inside of it.

I'm not sure how to proceed.

EDIT:

See Andrea's answer which worked for me. I ended up overriding the point method as suggested, and did not use hitTest. However, I went with another implementation of the point method override:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    if super.point(inside: point, with: event) { return true }
    for subview in subviews {
        let subviewPoint = subview.convert(point, from: self)
        if subview.point(inside: subviewPoint, with: event) { return true }
    }
    return false
}

Solution

  • As DonMag wrote:

    You cannot interact with an element that extends beyond the bounds of its parent (superview).

    At least not without overriding some methods, views have their own implementations to detect touches. Most important methods are:

    func hitTest(_ point: CGPoint, 
            with event: UIEvent?) -> UIView?
    
    func point(inside point: CGPoint, 
          with event: UIEvent?) -> Bool
    

    Those methods must be overriden, I've pasted some code on mine, is pretty old thus probably will require swift migration from 3.x to 4.x. Everything that you will add as a subview will handle touches even if is outside the bounds:

    class GreedyTouchView: UIView {
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            if !self.clipsToBounds && !self.isHidden && self.alpha > 0.0 {
                let subviews = self.subviews.reversed()
                for member in subviews {
                    let subPoint = member.convert(point, from: self)
                    if let result: UIView = member.hitTest(subPoint, with:event) {
                        return result
                    }
                }
            }
            return super.hitTest(point, with: event)
        }
        override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            return super.point(inside: point, with: event)
        }
    }
    


    But I'd like to point out that drop down menus are more Web UI related and not iOS UI related, you should use a picker instead.