Alternative Approach
This answer looks at a number of issues related to this occurrence and explains the debugging logic used to isolate and determine the
specific cause of the problem in my case. Along the way, I share other
possibilities that might have worked in other situations and explain
why this works in my situation.
tldr; Look at the self.definesPresentationContext = true, the
isBeingDismissed, isBeingPresented, canBecomeFirstResponder, and
delegate assignment of the SearchController, the Searchbar,
SearchResultsUpdater and their delegate methods.
(Source of self.definesPresentationContext
solution - See SO answer)
One thing to keep in mind is the context of how the SearchBar is being presented. Embedded in a toolbar, navigation bar, another UIView, as an input or input accessory view. All of which, I've found to have some impact on the timing and internal animation of the search bar as it is being presented or dismissed.
I've attempted all of the solutions presented and none of these worked until I reconsidered how I was trying to use the searchBar. In my case, I was pushing a controller (B) with a search controller from a controller (A) that already had an initial searchcontroller on it. I programmatically embedded each of the search controllers within the titleView of my navigation item when doing a pull refresh.
The answers suggesting adding searchbar.becomeFirstResponder()
into the life cycle didn't make sense for my use-case since the view was fully loaded by the time I wanted to insert and display my search bar into the navigationItem. The logic also seemed confusing since the view controller lifecycle methods should already be operating on the main thread. Adding a delay to the presentation also seemed to be an interference with the internal operations of the display and animation used by the system.
I found that calling my function to toggle the insertion worked when I pushed the view controller from controllerA but the keyboard would not display properly when pushed from controllerB. The difference between these two situations was that controllerA was a static tableview controller and controllerB had a tableview controller that I had made searchable by adding its own search controller.
e.g. controllerA with searchbar segues to controllerB with searchbar
Using a number of breakpoints and examining the status of the searchController and the searchbar at different points I was able to determine that the searchController.canBecomeFirstResponder
was returning false
. I also found that I needed to set the SearchResultsUpdater
to self
and the delegates on both the searchController and the searchBar.
I finally noted that setting self.definesPresentationContext = true
on controllerA was not allowing the keyboard to be displayed when I pushed controllerB onto the navigation stack. My solution was to move the self.definesPresentationContext = true
to viewDidAppear
on controllerA and in the prepare(for:sender:)
method of controllerA I change it to self.definesPresentationContext = false
when the destination is controllerB. This resolved the keyboard display issue in my case.
A word on animation ~ I've found that when assigning things to the navigationItem or navigationBar, the system has some built in timing and default animations. I avoid adding custom animation, code in moveToParent methods, or delayed presentations because unexpected behavior occurs in many cases.
Why this solution works
Apple documentation on definesPresentationContext
indicates the default behavior and notes some situations where this context adjusts the behavior the controller assigned to manage the keyboard appearance. In my case controllerA was assgined to manage the presentation rather than controllerB, so I just changed that behavior by adjusting this value:
When using the currentContext or overCurrentContext style to present a
view controller, this property controls which existing view controller
in your view controller hierarchy is actually covered by the new
content. When a context-based presentation occurs, UIKit starts at the
presenting view controller and walks up the view controller hierarchy.
If it finds a view controller whose value for this property is true,
it asks that view controller to present the new view controller. If no
view controller defines the presentation context, UIKit asks the
window’s root view controller to handle the presentation. The default
value for this property is false. Some system-provided view
controllers, such as UINavigationController, change the default value
to true.
becomeFirstResponder
a bit with dispatch_after()? – Ritualistsuper viewDidAppear
. – Tubercle