Search code examples
iosswiftxcodeuitableviewuitouch

SWIFT: detect touch outside current view


This is a relatively simple problem. I have a tableViewController which appears after the user presses a button, but it doesn't cover the entire screen, leaving some of the previous view controller visible. I was trying to use touchesEnded to dismiss the tableViewController, when the touchLocation.x property (the x coordinate of the CGPoint location of the touch) was past a certain point. Problem is, when the touch is past the x point (outside of the tableViewController), touchesEnded doesn't actually receive anything or get called, because it's outside the view, so my dismiss function doesn't get called. Does anyone know how to detect a touch outside of the current view, so that any touch outside the current view dismisses the said current view? Thanks


Solution

  • You need a clear view behind the table view, covering the screen, that can detect the tap outside the table view.

    There seems to be some difficulty grasping the general concept of how to do this, so here is a simple demonstration of the basic principle. This is a complete project; just copy it all and paste it right into a fresh vanilla project's view controller:

    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            self.view.backgroundColor = .yellow
            let b = UIButton(type: .system)
            b.setTitle("Present", for: .normal)
            b.sizeToFit()
            b.addTarget(self, action: #selector(tap), for: .touchUpInside)
            b.frame.origin = CGPoint(x: 100, y: 100)
            self.view.addSubview(b)
        }
        @objc func tap() {
            self.present(VC2(), animated: true)
        }
    }
    
    class VC2: UIViewController {
        init() {
            super.init(nibName: nil, bundle: nil)
            self.modalPresentationStyle = .overFullScreen
        }
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        override func viewDidLoad() {
            self.view.backgroundColor = .clear
            let tgr = UITapGestureRecognizer(target: self, action: #selector(tap))
            self.view.addGestureRecognizer(tgr)
            let child = VC3()
            self.addChild(child)
            self.view.addSubview(child.view)
            child.didMove(toParent: self)
            child.view.frame.size.width = 0.7*self.view.bounds.width
            child.view.frame.size.height = 0.7*self.view.bounds.height
            child.view.center = CGPoint(x:self.view.bounds.midX, y:self.view.bounds.midY)
            child.view.autoresizingMask = [.flexibleTopMargin, .flexibleBottomMargin, .flexibleLeftMargin, .flexibleRightMargin]
        }
        @objc func tap() {
            print("farewell")
            self.dismiss(animated: true)
        }
    }
    
    class VC3: UIViewController {
        init() {
            super.init(nibName: nil, bundle: nil)
        }
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        override func viewDidLoad() {
            self.view.backgroundColor = .green
            let tgr = UITapGestureRecognizer(target: self, action: #selector(tap))
            self.view.addGestureRecognizer(tgr)
        }
        @objc func tap() {
            print("ouch, stop that")
        }
    }
    

    Run the project and you will see a yellow view with a Present button; tap the Present button. A green view appears floating in the middle of the screen; notice that the yellow view is still visible behind it (that is part of your goal). Pretend that that is the table view. If you tap it, that tap is detected by VC3, which is our substitute for your table view controller. But if you tap outside it, the whole thing is dismissed (that is the other part of your goal).