Search code examples
iosswiftuikithittest

How do you pass a gesture between subviews in UIKit?


I've got a ViewController with three subviews. I'm trying to get them to detect touches in their bounds from a starting point outside their bounds without the user lifting their finger (ie the user dragging into the view). I thought hitTest would do this but it only works for separate taps. I assume this is probably passing a gesture through instead but I've not found out how to implement this.

class SuperViewController: UIViewController {

    var view01 = UIView(frame: CGRect(x: 0, y: 0, width: 1000,
                                          height: 800))
    var view02 = UIView(frame: CGRect(x: 0, y: 0, width: 600,
                                          height: 400))
    let view03 = UIView(frame: CGRect(x: 0, y: 0, width: 300,
                                      height: 200))
    
  
    
    override func viewDidLoad() {
        
        super.viewDidLoad()

        self.view = TestView()
        
        view01.backgroundColor = .orange
        view02.backgroundColor = .blue
        view03.backgroundColor = .green
        
        self.view.addSubview(view01)
        self.view.addSubview(view02)
        self.view.addSubview(view03)
    }
    
}

Which produces this enter image description here

And then I've subclassed UIView for the SuperViewController's view.

class TestView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.isUserInteractionEnabled, !isHidden, alpha > 0.01 else {return nil}
        
        if self.point(inside: point, with: event) {
            for subview in subviews.reversed() {

                let hitView = subview.hitTest(point, with: event)
                if hitView != nil {
                    hitView?.backgroundColor = .red
                    return hitView
                }
            }
            return self
        }
        return nil
    }
}

So each one turns red when the user taps. But ideally I want them to each respond with one drag from the top left corner of the screen to the other.


Solution

  • You can accomplish this with a UIPanGestureRecognizer.

    Here's an example below:

    class ViewController: UIViewController {
        
        var view01 = UIView(frame: CGRect(x: 0, y: 0, width: 1000,
                                              height: 800))
        var view02 = UIView(frame: CGRect(x: 0, y: 0, width: 600,
                                              height: 400))
        let view03 = UIView(frame: CGRect(x: 0, y: 0, width: 300,
                                          height: 200))
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            
            view01.backgroundColor = .orange
            view02.backgroundColor = .blue
            view03.backgroundColor = .green
            
            self.view.addSubview(view01)
            self.view.addSubview(view02)
            self.view.addSubview(view03)
            
            let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
            self.view.addGestureRecognizer(gestureRecognizer)
        }
    
        @objc
        private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
            guard let view = gestureRecognizer.view else {
                return
            }
            
            let translation = gestureRecognizer.translation(in: view)
            
            for subview in view.subviews.reversed() {
                if let hitView = subview.hitTest(translation, with: nil) {
                    hitView.backgroundColor = .red
                    return
                }
            }
        }
    }
    

    Pan