Search code examples
iosswiftuikitsubviewsuperview

UI table view is not responsive when I touch beyond its superview's position in iOS, UI Kit poroject


Context: In the iOS UIKit project, written in swift

lets say I have a Super view - 'A' with frame (0, 0, 320, 200)

and I have a subview say 'B' with frame (0, 0, 320, 600)

and the view 'B' is added as a subview to the view 'A'

now the subview 'B' is having a table view with in it

Issue facing

tablew view with in my subview B is responsive only till its super view position i.e. its scrollable and didSelectRowAt is invoked only within the frame (0, 0, 320, 200) which its super view frame

when I tap on any row beyond that frame, is not responding, could somebody please help me how do I fix this wired issue, thanks in advance.


Solution

  • Probably the best way to handle this is by overriding a hitTest method to return rather your target view. Something like this:

    class OverridingHitTestView: UIView {
        
        var forwardTo: UIView?
        
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            let originalResult = super.hitTest(point, with: event)
            if let forwardTo, originalResult == self {
                return forwardTo.hitTest(point, with: event)
            } else {
                return originalResult
            }
        }
        
    }
    

    This OverridingHitTestView will send events to whatever you set forwardTo view. In your case it should be the table view which is your sub view.

    Your OverridingHitTestView must be placed below your table view and it needs to be large enough to include all of the area you wish to handle.

    I created a trivial example all-in-code just to test run it.

    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            
            let container = OverridingHitTestView(frame: view.bounds)
            view.addSubview(container)
            
            let smallView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 300.0))
            smallView.backgroundColor = UIColor.green
            container.addSubview(smallView)
            
            let largeView = UIStackView(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 600.0))
            largeView.axis = .vertical
            largeView.distribution = .fillEqually
            (0..<10).forEach { index in
                let button = UIButton(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 60.0))
                button.setTitle("Button \(index+1)", for: .normal)
                button.addTarget(self, action: #selector(onButtonPressed), for: .touchUpInside)
                button.setTitleColor(.black, for: .normal)
                largeView.addArrangedSubview(button)
            }
            
            container.forwardTo = largeView
            
            smallView.addSubview(largeView)
        }
    
        @objc private func onButtonPressed(_ sender: UIButton) {
            print("Pressed button \(sender.titleLabel?.text ?? "[NA]")")
        }
    }
    
    class OverridingHitTestView: UIView {
        
        var forwardTo: UIView?
        
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            let originalResult = super.hitTest(point, with: event)
            if let forwardTo, originalResult == self {
                return forwardTo.hitTest(point, with: event)
            } else {
                return originalResult
            }
        }
        
    }
    

    To dig a bit more into the hit test:

    let originalResult = super.hitTest(point, with: event)
    if let forwardTo, originalResult == self {
        return forwardTo.hitTest(point, with: event)
    } else {
        return originalResult
    }
    

    The idea is that we check what would the view originally report. And if it would report self we forward the call to our target view. The reason for it is that self will be returned in cases where user did actually press within this view. And also that it did not press some subview on this view. This allows that other elements such as buttons still work correctly within this view (not all events are forwarded to the target view).

    I hope this sets you on the right path if not solve your issue. You could still use the same method but change conditions to for instance look for touch locations within a window or whatever really. The point being that you simply need to return a correct view on hitTest.