Search code examples
iosswiftuikituisearchcontrollerios11

In iOS 11, dismissing a modally presented navigation controller results in visual duplication


I have a modally presented UINavigationController. Onto that navigation controller, two view controllers are pushed. The whole modal presentation should be dismissed from the second view controller upon pressing a Done button. To do this, dismiss is called on the presentingViewController:

@IBAction func doneWasPressed(_ sender: UIBarButtonItem) {
    presentingViewController!.dismiss(animated: true)
}

In iOS 11, when an ancestor within the navigation controller has presented a UISearchController, which is added to the navigationItem, dismissing the navigation controller causes a visual artefact whereby a duplicate of the view is visible underneath the animating view. Once the top view has moved out of view, the duplicate underneath suddenly disappears. See the gif below for a screen recording of the issue.

A small reproducible example is available in this GitHub repository. It was created on Xcode 9 (GM) using Swift 4.

Is there a known workaround?



Solution

  • In preparing your minimal, complete, and verifiable example, you noticed that the problem was the presence of the UISearchController. I noticed that even after I pushed to the second page view controller (past the view controller with the UISearchController), that the search controller was still there:

    search controller still in hierarchy

    If you set the isActive to false when the view disappears, this problem goes away.

    override func viewDidLoad() {
        super.viewDidLoad()
    
        let searchController = UISearchController(searchResultsController: nil)
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.hidesNavigationBarDuringPresentation = false
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = false
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
    
        navigationItem.searchController?.isActive = false
    }
    

    And, when this is done, the dismissal works as expected.


    My original answer, suggesting that you want to dismiss the navigation controller, not the presenting view controller, is below.


    If you presented the navigation controller modally, and then pushed a few view controllers from there, you don't want to dismiss the presentingViewController, but rather the navigationController, e.g.:

    navigationController?.dismiss(animated: true)
    

    Or, alternatively, create an unwind action on your view controller to which you want to return:

    @IBAction func unwindHome(_ segue: UIStoryboardSegue) {
        // this is intentionally blank
    }
    

    Then you can create an unwind segue right in IB:

    enter image description here

    Or if you have to do it programmatically, create an unwind segue between the view controller and the "exit" outlet, and then you can performSegue(withIdentifier:sender:).