Search code examples
iosswift3uipresentationcontrolleruipresentingcontroller

iOS - Dismiss presented view controller touching outside its View


I have a CustomPresentationController which animates in and out with custom animations;

This specific controller gets presented, more less at 50% of the screen size, and when I present it, I add a shadow-gray view to the presentingViewController so it adds some depth.

I can only dismiss the presentedViewController if I tap the cancel button in the NavBar which I call the default dismiss(:) method.

What I'm trying to accomplish is to detect a tap outside the presentedViewController, maybe inside the gray zone, so I can dismiss the presentedViewController, somehow like dismissing an ActionSheet but I've failed to do it. Let me explain what I've tried so far.

I tried to add a UITapGestureRecognizer to the shadow-gray view but since I'm presenting a different controller, the app-engine might think that since the shadow view isn't on the top hierarchy view it might not be accessible so it 'blocks' the recognizer - whenever I tap it, the gesture handles doesn't fire.

I'm implementing now in addition a swipe down to dismiss, which I can make it easily, but I really wanted the tap-outside feature to work as well.

Any hint on how can I approach this?

The apps image is the following:

screenshot


Solution

  • I just had to implement this in one of my app.

    I made it worked by adding a button that covers the entire view and this button, once tapped triggers the VC to be dismissed.

    Once the button is added you can add your custom View on top.

    So far it looks like it's working pretty well.

    My code below (I do everything programmatically, no storyboard)

        //—————————————————————————————
        // MARK: View Life Cycle
        //—————————————————————————————
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.clear //VC view background transparent
        setupUI()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    
        //Animate blackView opacity to 1 to give some depth
        UIView.animate(withDuration: 0.4, delay: 0.2, options: .curveEaseInOut, animations: {
            self.blackView.alpha = 1 
        })
    }
    
        //————————————————
        // MARK: Setup UI
        //————————————————
    
        let blackView: UIView = {
            let view = UIView()
            view.alpha = 0.0
            view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
            return view
        }()
        //Invisible button which covers the entire view that can be tapped 
        lazy var dismissLayerBtn: UIButton = {
            let btn = UIButton()
            btn.addTarget(self, action: #selector(tapToDismiss), for: .touchUpInside)
            return btn
        }()
    
        @objc func tapToDismiss() {
            print("tapToDimiss")
            self.dismiss(animated: true, completion: nil)
        }
    
        let milestonePickerView: MilestonePickerView = {
            let view = MilestonePickerView(frame: .zero)
            return view
        }()
    
        func setupUI() {
    
            view.addSubview(blackView)
            view.addSubview(dismissLayerBtn)
            view.addSubview(milestonePickerView) //Important to add the customView after the button.
    
            blackView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
    
            dismissLayerBtn.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
    
            milestonePickerView.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 40, paddingRight: 20, width: 0, height: 400)
    
    //I'm using a custom extension to setup constraints (anchors) 
       } 
    

    If you're using storyboard, make sure you put the invisible button under the custom view.

    I hope this helps.