77

This is my first application for iOS.

So I have a UIVIewController with a UITableView where I have integrated a UISearchBar and a UISearchController in order to filter TableCells to display

override func viewDidLoad() {
    menuBar.delegate = self
    table.dataSource = self
    table.delegate = self
    let nib = UINib(nibName: "ItemCellTableViewCell", bundle: nil)
    table.registerNib(nib, forCellReuseIdentifier: "Cell")

    let searchButton = UIBarButtonItem(barButtonSystemItem: .Search, target: self, action: "search:")
    menuBar.topItem?.leftBarButtonItem = searchButton
    self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)
        controller.searchResultsUpdater = self
        controller.dimsBackgroundDuringPresentation = false
        return controller
    })()
    self.table.reloadData()
}

I am using also a modal segue in order to open the element's ViewController where I will display details of the element.

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.index = indexPath.row
    self.performSegueWithIdentifier("ItemDetailFromHome", sender: self)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if (segue.identifier == "ItemDetailFromHome") {
        let settingsVC = segue.destinationViewController as! ItemDetailViewController
        settingsVC.parent = self
        if self.isSearching == true  && self.searchText != nil && self.searchText != ""  {
            settingsVC.item = self.filteredItems[self.index!]
        } else {
            settingsVC.item = self.items[self.index!]
        }

    }
}

That works fine until I try to display the ItemDetailViewController for a filtered element (through the UISearchController).

I have the following message :

Warning: Attempt to present <ItemDetailViewController: *>  on <HomeViewController: *> which is already presenting (null)

At every time I am going to the ItemDetailViewController.viewDidLoad() function but after that when the search is activated I have the previous error.

Any idea ? I have tried to use the following async dispatch but without success

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.index = indexPath.row
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.performSegueWithIdentifier("ItemDetailFromHome", sender: self)
    })
}
Nat
  • 12,032
  • 9
  • 56
  • 103
Splendf
  • 1,611
  • 1
  • 10
  • 8
  • if you are setting ItemDetailFromHome from the tableview controller no need to call self.performSegueWithIdentifier("ItemDetailFromHome", sender: self) in did select row at index path – Johnykutty Sep 21 '15 at 13:55
  • In fact the `ItemDetailViewController` will be a view used by several segue/viewController (mutualized). – Splendf Sep 21 '15 at 14:12
  • Great, and how I can do that ? in cellForRowAtIndexPath ? I have just seen examples with performSegue – Splendf Sep 21 '15 at 14:22
  • 1
    If you have added the segue from tableview cell in storyboard, then no need to write self.performSegue... your code. If you have added segue from the view controller, its needed to do so – Johnykutty Jan 03 '17 at 12:55
  • @Johnykutty Thank you! When I was refactoring a project, I discovered I had this very problem. Thank you for posting such a simple solution. – Adrian Mar 08 '17 at 05:07

18 Answers18

83

I have found out a solution.

I have add the following code in HomeViewController.viewDidLoad and that works !

definesPresentationContext = true
Splendf
  • 1,611
  • 1
  • 10
  • 8
  • 12
    @Shahar this works because `definesPresentationContext ` is basically saying "I'm the view controller to be use when you presenting another modal view controller". Otherwise, it will travel up the view hierarchy to find who ever does have `definesPresentationContext` set to true and use that one to present, and he might already be presenting ... – cohen72 Oct 22 '17 at 09:14
  • 1
    for some reason, this only worked after embedding the viewcontroller in a uinavigationcontroller – Crashalot Nov 23 '17 at 00:24
  • 1
    My app was working perfectly on iOS 10 and 11, then I tested it on an iPhone 4 and started showing this error. This solution worked as a charm. By the way, you can also set definesPresentationContext selecting the View Controller on the Storyboard Editor and enabling the checkbox "Defines context". – Felipe Ferri Jan 11 '18 at 00:34
  • finally a solution to a problem that bugged me a long time. thank you, sir. – iDoc Nov 03 '18 at 08:30
  • 1
    I consider this a hack because it is curing the symptom and not the cause. – Mojo66 May 28 '20 at 11:06
  • this is a hack because the success depends on a viewControllers hierarchy – Blazej SLEBODA Jul 31 '20 at 13:00
37

In my case, I found my code to present the new viewController (a UIAlertController) was being called twice.

Check this before messing about with definesPresentationContext.

grahamparks
  • 16,130
  • 5
  • 49
  • 43
  • 11
    Seems almost silly, but I bet in a vast majority of cases this will be the correct answer. Sure enough I found a 2nd call... – the_dude_abides Feb 16 '17 at 05:01
  • 1
    Yeah, in some cases we need to check for `presentedViewController` before presenting – onmyway133 Feb 05 '18 at 14:29
  • In my case it didn't get called twice but I called present() inside shouldPerformSegue() and forgot to return false to prevent the storyboard segue to present something too. – randomcontrol Sep 29 '19 at 14:01
  • We'll I'll be buggered. We found a second call to `present`, as well. – Aaron Dec 18 '20 at 22:39
13

In my case, I tried too early to show the new UIViewController before closing the previous one. The problem was solved through a call with a slight delay:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
     self.callMethod()
}
Pavel Kataykin
  • 1,527
  • 15
  • 14
  • 32
    I really don't recommend using a random 0.3 second delay. Try and find the correct method after which you can present (viewDidAppear) instead of hoping that the hierarchy is ready after 0.3 seconds – Allison Jul 01 '19 at 18:11
  • This answer should be downvoted for being an ugly hack. – Mojo66 May 28 '20 at 11:04
  • 3
    In my case it allowed me to see the second view controller that presenting has been added by a mistake. It might be used not as a solution but as a debug tool. – Rostyslav Druzhchenko Feb 05 '21 at 08:10
7

The problem for me is that I was presenting two modals and I have to dismiss both and then execute some code in the parent window ... and then I have this error... I solved it seting this code in the dismiss o the last modal presented:

self.dismiss(animated: true, completion: {
                self.delegate?.callingDelegate()
            })

in other words instead of just dismiss two times .. in the completion block of the first dismiss call delegate that will execute the second dismiss.

Hiram Elguezabal
  • 143
  • 1
  • 3
  • 8
7

What worked for me was to add the presentation of the alert to the main thread.

DispatchQueue.main.async {
   self.present(alert, animated: true)
}

The presentation of the current viewController was not complete. By adding the alert to the main thread it can wait for the viewController's presentation to complete before attempting to present.

Chris Herbst
  • 1,241
  • 1
  • 16
  • 28
5

I got the same issue when i tried to present a VC which called inside the SideMenu(jonkykong).

first i tried inside the SideMenu and i called it from the delegate to the MainVC both had the same issue.

Solution: dismiss the SideMenu first and present the new VC after will works perfectly!.

Lahiru Pinto
  • 1,621
  • 19
  • 20
4

This happened with me on our project. I was presenting our log in/log out ViewController as a pop-over. But whenever I tried to log back out again and display the pop-over again, I was getting this logged out in my console:

Warning: Attempt to present UIViewController on <MY_HOME_VIEW_CONTROLLER> which is already presenting (null)

My guess is that the pop-over was still being held by my ViewController even though it was not visible.

However you are attempting to display the new ViewController, the following code I used to solve the issue should work for you:

func showLoginForm() {

    // Dismiss the Old
    if let presented = self.presentedViewController {
        presented.removeFromParentViewController()
    }

    // Present the New
    let storyboard = UIStoryboard(name: "MPTLogin", bundle: Bundle(for: MPTLogin.self))
    let loginVC = storyboard.instantiateViewController(withIdentifier: "LogInViewController") as? MPTLogInViewController
    let loginNav = MPTLoginNav(rootViewController: loginVC!)
    loginNav.modalPresentationStyle = .pageSheet;
    self.present(loginNav, animated: true, completion: nil)
}
Brandon A
  • 8,153
  • 3
  • 42
  • 77
  • 1
    My problem was very similar. It turned out I was following a call to dismiss one view controller too closely with a call to present another. The misleading part was that it would actually work fine nine times out of ten! – Wade May 22 '18 at 05:31
3

Building on Mehrdad's answer: I had to first check if the search controller is active (if the user is currently searching):

if self.searchController.isActive {
    self.searchController.present(alert, animated: true, completion: nil)
} else {
    self.present(alert, animated: true, completion: nil)
}

where alert is the view controller to present modally.

smörkex
  • 336
  • 3
  • 18
  • This was exactly my problem. I was always presenting the new view controller (PHPickerViewController in my case) directly from the app view controller, even if a search controller was active. The way I coded the fix was to call self.navigationController!.visibleViewController!.present. – xirix Apr 28 '21 at 01:09
2

I faced the same kind of problem What I did is from Interface builder selected my segue Its kind was "Present Modally" and its presentation was "Over current context"

i changed the presentation to "Default", and then it worked for me.

2

In my case I was trying to present a UIAlertController at some point in the app's lifetime after using a UISearchController in the same UINavigationController.

I wasn't using the UISearchController correctly and forgot to set searchController.isActive = false before dismissing. Later on in the app I tried to present the alert but the search controller, though not visible at the time, was still controlling the presentation context.

Clay Ellis
  • 4,960
  • 2
  • 37
  • 45
  • 1
    So relieved to come across your answer; was wracking my brain trying to figure it out and mine turned out to be the `UISearchController` too. – James Toomey Jun 27 '18 at 22:20
  • @JamesToomey glad I could help! I spent an inordinate amount of time wracking my own before realizing what was happening... – Clay Ellis Jul 02 '18 at 20:32
  • If you are struggling with a uisearchcontroller THIS is the answer! – Carioni Oct 26 '18 at 21:19
2

My problem was that (in my coordinator) i had presented a VC on a VC and then when i wanted to present the next VC(third one), presented the third VC from the first one which obviously makes the problem which is already presenting. make sure you are presenting the third one from the second VC.

secondVC.present(thirdVC, animated: true, completion: nil)
Mehrdad
  • 1,050
  • 1
  • 16
  • 27
0

This is what finally worked for me, as my project didn't exactly have a NavigationVC but instead, individual detached VC's. as xib files

This code produced the bug:

present(alertVC, animated: true, completion: nil)

This code fixed the bug:

 if presentedViewController == nil{
        navigationController?.present(alertVC, animated: true, completion: nil)
    }
KarmaDeli
  • 610
  • 1
  • 6
  • 16
0

For me it was an alert that was interfering with the new VC that I was about to present.

So I moved the new VC present code into the OK part of my alert, Like this :

    func showSuccessfullSignupAndGoToMainView(){

    let alert = UIAlertController(title: "Alert", message: "Sign up was successfull.", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
        switch action.style{
        case .default:
            // Goto Main Page to show businesses
            let mainStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
            let vc : MainViewController = mainStoryboard.instantiateViewController(withIdentifier: "MainViewController") as! MainViewController
            self.present(vc, animated: false, completion: nil)

        case .cancel:
            print("cancel")

        case .destructive:
            print("destructive")

        }}))
    self.present(alert, animated: true, completion: nil)
}
Iman Nia
  • 2,255
  • 2
  • 15
  • 35
0

My issue was that I was trying to present an alert from a view that wasn't on top. Make sure you present from the top-most viewController.

powertoold
  • 1,593
  • 13
  • 19
0

In my case this was an issue of a button which was duplicated in Interface Builder. The original button had a touch-up handler attached, which also presented a modal view. When I then attached a touch-up handler on the copied button, I forgot to remove the copied handler from the original, causing both handlers to be fired and thus creating the warning.

Mads Mobæk
  • 34,762
  • 20
  • 71
  • 78
0

More than likely you have your Search button wired directly to the other view controller with a segue and you are calling performSegueWithIdentifier. So you are opening it twice, which generates the error that tells you "is already presenting."

So don't call performSegueWithIdentifier, and that should do the trick.

Rydell
  • 4,209
  • 7
  • 28
  • 30
0

Make sure you Dismiss previous one before presenting new one!

Mahdi Giveie
  • 630
  • 1
  • 8
  • 25
0

This one fixed the error, but I still have to double-click the button to call VC. Button unavailable for a few ms. ((

let secondTVC = SecondTVC()
@objc func secondBtnAction() {
    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }
        if self.presentedViewController == nil {
            let modalNav = UINavigationController(rootViewController: self.secondTVC)
            self.navigationController?.show(modalNav, sender: AnyObject.self)
        }
    }
}
zef_s
  • 11
  • 2