82

I have code that creates a UISearchController' in my UIVIew'sviewDidLoad`.

 self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)
        controller.searchResultsUpdater = self
        controller.searchBar.delegate = self
        controller.dimsBackgroundDuringPresentation = false
        controller.searchBar.sizeToFit()
        controller.hidesNavigationBarDuringPresentation = false //prevent search bar from moving
        controller.searchBar.placeholder = "Search for song"

        self.myTableView.tableHeaderView = controller.searchBar

        return controller

    })()

Right after this closure finishes, this warning appears in the console:

Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UISearchController: 0x154d39700>)

I don't get what I am doing wrong. This similar question is not really my situation (At least I don't think so). What is going on?

Community
  • 1
  • 1
MortalMan
  • 2,582
  • 3
  • 23
  • 47
  • https://xkcd.com/583/ Works ok if I throw that into my Table VC `viewDidLoad()`. Recommend a) including entire VC source listing and b) ensuring that the error is really happening where and when you think it is. – BaseZen Aug 29 '15 at 05:02
  • Also, do more research such as: http://stackoverflow.com/questions/31006045/view-appear-with-modal-view-animation-instead-of-show-push-animation which has the same error – BaseZen Aug 29 '15 at 05:04
  • So, I made a swift project, did everything progrmatically, no storyboards, and I have no problem, is this a storyboard's issue you are having, perhaps, I don't know, but I assume you are using storyboards, right? When I say programmatically, I mean no nibs, no storyboards, all code and it works fine – Larry Pickles Aug 29 '15 at 08:25
  • @BaseZen I set a breakpoint before `})()` and after `})()`. The error gets thrown after the closure ends. I have a `UIViewController`, not a `tableViewController`. – MortalMan Aug 29 '15 at 13:21
  • @Larcerax I do have one storyboard. It contains only a Navigation Controller and a UIViewController (they are connected.) – MortalMan Aug 29 '15 at 13:24
  • Just a note: there is no reason at all to use a closure here. – fishinear Aug 29 '15 at 17:07

13 Answers13

120

UISearchController's view has to be removed from its superview before deallocate. (guess it is a bug)

Objective-C...

-(void)dealloc { 
    [searchController.view removeFromSuperview]; // It works!
}

Swift 3...

deinit {
    self.searchController.view.removeFromSuperview()
}

I struggled with this issue for a couple of weeks. ^^

Murray Sagal
  • 8,454
  • 4
  • 47
  • 48
JJH
  • 1,201
  • 1
  • 7
  • 2
  • 1
    This is really weird... But it did the trick for me too. I guess it is a bug indeed. – Mihai Fratu Oct 12 '15 at 16:50
  • 22
    I had the same problem with a `UISearchControler` I was allocating in `-viewDidLoad`. It's definitely a bug - if the `UISearchController` is deallocated before its view is loaded, this warning will appear. If I tap into the search field (thereby loading the view), it does not appear. So in my `dealloc`, I call `[self.searchController loadViewIfNeeded]` (new in iOS 9), – Leehro Oct 19 '15 at 01:29
  • 4
    @Leehro's comment is the answer for me. it came out as `if #available(iOS 9.0, *) { self.searchController?.loadViewIfNeeded() }` – Tim Oct 31 '15 at 19:18
  • 6
    I'd add to @Leehro's comment that there's no need to do this in dealloc, you can do the loadViewIfNeeded in viewDidLoad instead. – Clafou Dec 03 '15 at 22:29
  • Thanks to @Leehro and @Clafou... adding the call `[self.searchController loadViewIfNeeded];` (Obj-C) is the answer that solved the problem in my code. – andrewbuilder Mar 14 '16 at 01:17
  • Thanks @Leehro, this helped me out. – Andrew Hershberger Jun 23 '16 at 20:48
  • It is weird, that forcing to remove the view from its superview - I've tried to ask the search controller with 'if ([searchController isViewLoaded])' which returned always nil in dealloc. So forcing to remove it from superview did the trick. I think it must be a bug - they are still not capable to add a UISearchController with IB, even not with Xcode 8! – konran Oct 16 '16 at 16:57
  • It's not a bug, it's an intentional behaviour to reduce memory consumption. View is not loaded until searchController hasn't become active. Nevertheless `loadViewIfNeeded` suppress this warning I recommend using approach from @JJH answer. – Terry Jun 15 '17 at 12:33
36

Solved! It was a simple fix. I changed this code

class ViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

    var resultSearchController = UISearchController()

to this:

 class ViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

    var resultSearchController: UISearchController!

This fixes the problem.

BaseZen
  • 8,650
  • 3
  • 35
  • 47
MortalMan
  • 2,582
  • 3
  • 23
  • 47
  • 1
    I changed it to what you meant. :-) Anyway that's why including the whole source in the question is better, but finding it on your own is best ;-) – BaseZen Aug 29 '15 at 14:03
  • I was having this annoying warning too and, following your answer, I fixed everything. But I don't know why the ! is needed instead of allocating a new UISearchController...would you exaplain me? – SagittariusA Sep 21 '15 at 16:21
  • Not sure, I would like an explanation too. – MortalMan Sep 21 '15 at 16:27
  • If I change from var resultSearchController = UISearchController() to var resultSearchController: UISearchController! I get fatal error: unexpectedly found nil while unwrapping an Optional value in noOfRowInSection. I have large number of array, is that creating error? suggest me. – Pawriwes Oct 01 '15 at 16:06
  • I figured out, instead of doing delegate and datasource from storyboard, I wrote in the code and the problem was solved. – Pawriwes Oct 01 '15 at 16:52
  • +1 on why? Why does declaring the UISearchController as an implicitly unwrapped optional instead of instantiating it silence this warning? – maml Oct 15 '15 at 22:48
  • This change worked for me as well, although I'm not understanding why either. +1 on question above... – user1082348 Nov 28 '15 at 17:29
  • I had the same issue and your answer worked!. But I don't understand why declaring it as an implicit unwrapper optional solves the issue – TMS Apr 15 '16 at 06:14
  • It works like a charm! Thanks. It is somehow related to loading the view at an inappropriate time. I think it works because it gets initialised guaranteed NOT before the view itself got loaded, but only after. – Burak Jun 06 '16 at 14:30
20

Here is the Swift version that worked for me (similar toJJHs answer):

deinit{
    if let superView = resultSearchController.view.superview
    {
        superView.removeFromSuperview()
    }
}
nijm
  • 2,158
  • 12
  • 28
  • @alex I put it at the end of the view controller that initializes the resultSearchController. – nijm Jan 19 '16 at 15:10
  • 2
    You don't really NEED the `if let` since `.removeFromSuperView()` won't do anything if `superview == nil` – NSGangster Jun 09 '16 at 14:47
11
class SampleClass: UITableViewController, UISearchBarDelegate {

private let searchController =  UISearchController(searchResultsController: nil)

 override func viewDidLoad() {
        super.viewDidLoad()

        searchController.loadViewIfNeeded() // Add this line before accessing searchController
 }

}
harsh_v
  • 3,193
  • 3
  • 34
  • 53
10

Hacking together a few solutions I managed to get mine working by adding lines to viewDidLoad before fully setting up the UISearchController:

override func viewDidLoad() {
    super.viewDidLoad()
    self.navigationItem.rightBarButtonItem = self.editButtonItem()

    if #available(iOS 9.0, *) {
        self.resultSearchController.loadViewIfNeeded()// iOS 9
    } else {
        // Fallback on earlier versions
        let _ = self.resultSearchController.view          // iOS 8
    }
    self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)
        controller.searchResultsUpdater = self
        controller.dimsBackgroundDuringPresentation = false
        controller.searchBar.sizeToFit()

        self.tableView.tableHeaderView = controller.searchBar

        return controller
    })()

    self.tableView.reloadData()

}
Derek
  • 2,092
  • 1
  • 24
  • 38
  • I also tried every other solution given here, and none did suppress the warning (although the search controller **does** work at runtime); this did it. Thank you! – Nicolas Miari Nov 20 '15 at 05:48
  • This has helped me as well, but I had to add that line _after_ I initialise UISearchController. `self.searchController = ({ let controller = UISearchController(searchResultsController: nil) controller.searchResultsUpdater = self controller.dimsBackgroundDuringPresentation = false controller.searchBar.delegate = self definesPresentationContext = true controller.searchBar.sizeToFit() return controller })()` then `self.searchController.loadViewIfNeeded()` – yuzer May 26 '16 at 17:25
  • Why do we need to initialize in the block? – code4latte Feb 17 '17 at 19:51
7

In Swift2 I got the same error message due to an obvious bug:

let alertController = UIAlertController(title: "Oops",
    message:"bla.", preferredStyle: UIAlertControllerStyle.Alert)

alertController.addAction(UIAlertAction(title: "Ok", 
     style: UIAlertActionStyle.Default,handler: nil))

self.presentViewController(alertController, animated: true, completion: nil)

Due to a stupid copy error from myself, I had not included the self.presentViewController line. This caused the same error.

Vincent
  • 4,342
  • 1
  • 38
  • 37
7

In Swift 2.2 version that worked for me

deinit {
    self.searchController?.view.removeFromSuperview()
}

I think it's helpful!

Milos Mandic
  • 1,046
  • 2
  • 13
  • 19
2

It's not a bug. It seems that you have to avoid creating ViewControllers without presenting them. So after SomeViewController() or let variable: SomeViewController you have to call something like this self.presentViewController(yourViewController ...etc). If you don't do that, you will get this warning when this view controller will be dealocated.

2

Creating a search controller in viewDidLoad() and setting its search bar as the navigation item's title view doesn't create a strong reference to the search controller, which is why it's deallocated.

So instead of doing this:

override func viewDidLoad() {
    super.viewDidLoad()
    // Create search controller
    let searchController = UISearchController(searchResultsController: nil)
    // Add search bar to navigation bar
    navigationItem.titleView = searchController.searchBar
    // Size search bar
    searchController.searchBar.sizeToFit()
}

You should do this:

var searchController: UISearchController!

override func viewDidLoad() {
    super.viewDidLoad()
    // Create search controller
    searchController = UISearchController(searchResultsController: nil)
    // Add search bar to navigation bar
    navigationItem.titleView = searchController.searchBar
    // Size search bar
    searchController.searchBar.sizeToFit()
}
Niels
  • 301
  • 2
  • 6
2

Mine is working like this

func initSearchControl(){

        searchController = UISearchController(searchResultsController: nil)

        if #available(iOS 9.0, *) {
            searchController.loadViewIfNeeded()
        } else {
            let _ = self.searchController.view
        }

        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        definesPresentationContext = true
        tableView.tableHeaderView = searchController.searchBar
        searchController.searchBar.sizeToFit()
    }

searchController.loadViewIfNeeded() solves the problem but you need to call it after initializing the searchController

mehmetsen80
  • 727
  • 1
  • 8
  • 25
1

I used Derek's answer, but had to change it slightly. The answer that was provided crashed for me because the call to loadViewIfNeeded() happened before the resultSearchController was defined. (My declaration was

var resultSearchController: UISearchController!

). So I just moved it afterwards and it worked.

If I left out the call entirely, the bug remained, so I'm sure it is an essential part of the answer. I was unable to test it on iOS 8.

1

It seem the view is lazy loaded, if you allocated the controller and never show it, the view is not loaded. In this case, if the controller is deallocated, you will received this warning. you could show it once, or call it's loadViewIfNeed() method, or use 'let _ = controller.view' to force load the view to avoid this warning.

david
  • 391
  • 3
  • 11
0

I'm a bit late to the party, but here's my solution:

var resultSearchController: UISearchController!

override func viewDidLoad()
{
    super.viewDidLoad()

    self.resultSearchController = ({
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
        searchController.searchBar.sizeToFit()
        return searchController
    })()

    self.tableView.tableHeaderView = self.resultSearchController.searchBar
    self.tableView.reloadData()
}

I hope it works for you.

titusmagnus
  • 2,014
  • 3
  • 23
  • 23