UISearchController searchBar showsCancelButton not being respected - ios

I've added a UISearchController to my application and set it's searchBar to the titleView of my navigationItem.
This works but I am seeing the cancel button despite having set showsCancelButton to false.
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsUpdater
// Configure the searchBar
searchController.searchBar.placeholder = "Find Friends..."
searchController.searchBar.sizeToFit()
searchController.searchBar.showsCancelButton = false
self.definesPresentationContext = true
navigationItem.titleView = searchController.searchBar

I agree, it seems like a bug. The problem is that the searchController keeps resetting the showsCancelButton property of the searchBar. I found a solution that involves:
subclassing UISearchBar to ignore setShowsCancelButton.
to make the searchController use that subclass, you have to subclass UISearchController.
And then you find that the searchBar is not triggering the search controller's delegate methods, so you have to trigger them separately...
Convoluted, but it seems to do the trick. You can find the full answer here.

This appears to be a bug in iOS. The same behavior I've described can be seen in the example project supplied by Apple
https://developer.apple.com/library/ios/samplecode/TableSearch_UISearchController/Introduction/Intro.html
The documentation states that the default for this is NO but this doesn't seem to be the case. Setting showsCancelButton to NO seems to have no effect.
I have filed a radar for this and am waiting to hear back.

Easy solution in Swift3 - we need to make CustomSearchBar without cancel button and then override the corresponding property in new CustomSearchController:
class CustomSearchBar: UISearchBar {
override func setShowsCancelButton(_ showsCancelButton: Bool, animated: Bool) {
super.setShowsCancelButton(false, animated: false)
}}
class CustomSearchController: UISearchController {
lazy var _searchBar: CustomSearchBar = {
[unowned self] in
let customSearchBar = CustomSearchBar(frame: CGRect.zero)
return customSearchBar
}()
override var searchBar: UISearchBar {
get {
return _searchBar
}
}}
In MyViewController I initialize and configure searchController using this new custom subclass:
var mySearchController: UISearchController = ({
// Display search results in a separate view controller
// let storyBoard = UIStoryboard(name: "Main", bundle: Bundle.main)
// let alternateController = storyBoard.instantiateViewController(withIdentifier: "aTV") as! AlternateTableViewController
// let controller = UISearchController(searchResultsController: alternateController)
let controller = CustomSearchController(searchResultsController: nil)
controller.searchBar.placeholder = NSLocalizedString("Enter keyword (e.g. iceland)", comment: "")
controller.hidesNavigationBarDuringPresentation = false
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.searchBarStyle = .minimal
controller.searchBar.sizeToFit()
return controller
})()

I had to correct by putting in a little hack...
Setting the alpha to 0.0 on viewDidLoad because he screen will flash.
Before you ask...willPresentSearchController will not work.
extension GDSearchTableViewController: UISearchControllerDelegate {
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.setShowsCancelButton(false, animated: false)
searchController.searchBar.becomeFirstResponder()
UIView.animateWithDuration(0.1) { () -> Void in
self.view.alpha = 1.0
searchController.searchBar.alpha = 1.0
}
}
}

We wanted the search bar to have no Cancel button initially, but have it appear when the user tapped in the search bar.
Then we wanted the Cancel button to disappear if user tapped Cancel, or otherwise the search bar lost first responder.
What finally worked for me:
On create:
searchBar.showsCancelButton = NO;
We use a subclass of UISearchBar and override searchBarShouldBeginEditing thusly:
-(BOOL)searchBarShouldBeginEditing:(UISearchBar*)searchBar {
self.showsCancelButton = YES;
return YES;
}
We also override resignFirstReponder (in the UISearchBar subclass) thusly:
-(BOOL)resignFirstResponder
{
self.showsCancelButton = NO;
return [super resignFirstResponder];
}

I would also add
searchController.hidesNavigationBarDuringPresentation = false
searchController.delegate = self
searchController.searchBar.delegate = self
See if assigning those delegates will help.

I try to help you man but I'm not sure that I find the real problem.
According to Apple Documentation:
showsCancelButton
boolean property that indicating whether the cancel button is
displayed
But for hide the cancel button maybe you should use:
setShowsCancelButton(_:animated:)
I hope that can be helpful.

You can subclass UISearchBar and override method layoutSubviews
super.layoutSubviews()
self.showsCancelButton = false

my solution was to set the attribute every time anew when I used the searchcontroller respectively its searchbar. I initialized the searchcontroller lazily without setting the attribute and then did
searchController.searchBar.showsCancelButton = false
every time before search began.
You could do this in the UISearchControllerDelegate methods i.e...

This worked for me (iOS 10):
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.searchController.searchBar.showsCancelButton = NO;
}

It may be helpful to note that this has changed on iOS 13 and quote Apple's documentation on showsCancelButton, currently only available on UISearchBar.h and not on developer.apple.com
/* New behavior on iOS 13.
If the search bar is owned by a UISearchController, then using the setter
for this property (as well as -setShowsCancelButton:animated:) will implicitly
set the UISearchController's automaticallyShowsCancelButton property to NO.
*/
automaticallyShowsCancelButton has been introduced on iOS 13.0 and should clarify what #pbasdf had already pointed out in his answer: that the buggy behavior is something intrinsic to UISearchController.

What about setting it with [searchBar setShowsCancelButton:NO animated:NO];
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UISearchBar_Class/#//apple_ref/occ/instm/UISearchBar/setShowsCancelButton:animated:

Related

UIView hidden by USearchController

I am using UISearchController in a Large header style. But when I push to the next view controller and coming back to the same controller again my UIView hides by navigation bar.
See Video
https://drive.google.com/open?id=1tb5Eeni-79uomBGl1GPEcESDSYOfrZ7v
I can't reproduce your error, but I'll post my test code, as it can help you.
I suggest that you write this code in a BaseViewController: UIViewController and extend it in your ViewControllers for define Large Titles:
self.navigationController?.navigationBar.prefersLargeTitles = true
Define your UISearchController and set your Instance.
var resultSearchController: UISearchController!
resultSearchController = UISearchController(searchResultsController: nil)
resultSearchController.searchResultsUpdater = self
tableView.tableHeaderView = resultSearchController.searchBar
Finally, you can try to close the Search keyboard when the screen disappears, like this:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
resultSearchController.searchBar.endEditing(true)
}
Hope this helps.
Adding following line in viewwillappear resolves my issue
extendedLayoutIncludesOpaqueBars = true

iOS11 SearchController in NavigationBar with Scope Buttons

In iOS 11 you can put a UISearchController in the NavigationBar with a few lines of code.
I set up everything in the ViewController.swift.
func setupNavBar() {
navigationController?.navigationBar.prefersLargeTitles = true
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = wordViewController
searchController.searchBar.scopeButtonTitles = ["French", "English"]
searchController.searchBar.delegate = wordViewController
navigationItem.searchController = searchController
// Make searchbar persistent
navigationItem.hidesSearchBarWhenScrolling = false
}
In my delegate, the search fires and filters properly. However, if I click either of the scope buttons, they simply disappear. This delegate method is never called. (filter by scope is not actually implemented yet)
extension WordViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
if let searchText = searchBar.text {
print("Scoped changed: \(searchText)")
filteredWordList = wordList.filter({$0.contains(searchText)})
}
}
}
Full source is on Github:
https://github.com/melling/ios_topics/tree/master/NavBarSearch
https://github.com/melling/ios_topics/tree/master/NavBarSearch/NavBarSearch
Try to add this code within setupNavBar():
searchController.dimsBackgroundDuringPresentation = false
I was able to use Artem's solution,
searchController.dimsBackgroundDuringPresentation = false
but I then ran into the problem of no longer being able to click on the background to dismiss the focus on the search bar.
To resolve this, I created my own dimmer uiview (screen size, background color of black with 25% opacity), and added that to the view of the currently presented screen, and listened for a tap. When the user taps it, I fade it out and set
searchController.active = false
In your ViewController, you need to add this line
self.definesPresentationContext = YES;
This tells the UISearchController the presentation context it needs to use, which is defined by your ViewController.
Check this link for more details: http://asciiwwdc.com/2014/sessions/228

iOS 9 searchBar disappears from table header view when UISearchController is active

The structure:
View1 (click a button) -> present modally (MyModalView: UITableViewController)
MyModalView has UISearchController embedded. The searchBar of UISearchController is placed in MyModalView.tableView.tableHeaderView.
It's been working fine since iOS 8.0. However on iOS 9, the searchBar disappear when the UISearchController is active. Please take a look at theses pictures below
The modal view:
UISearchController active on iOS 8:
UISearchController active on iOS 9:
The very standard code:
override func viewDidLoad() {
super.viewDidLoad()
// Dynamically create a search controller using anonymous function
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.searchBar.delegate = self
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
// Auto sizing row & cell height
self.tableView.estimatedRowHeight = 130
self.tableView.rowHeight = UITableViewAutomaticDimension
self.definesPresentationContext = true
// No footer for better presentation
self.tableView.tableFooterView = UIView.init(frame: CGRectZero)
}
This issue also happens in iOS 9.1 beta...
Any idea / pointer would be deeply appreciated
Cheers.
I'm not sure what exactly is the problem but I 'fixed' it by:
self.searchController.hidesNavigationBarDuringPresentation = NO;
self.definesPresentationContext = NO;
My guess is that UISearchController is doing something funky when it is trying to present as a navigation bar. So, this is a hack but it at least doesn't block the user. The search bar doesn't do the cool animation and cover up the navigation bar.
It seems all of us got the same problem but they were solved in different ways. However none of the suggested answers worked for me :(. Nevertheless thank you all for your time.
I got a solution that solved my problem. It is setting Extend Edges - Under Opaque Bars of my (MyModalView: UITableViewController) to true in the Storyboard using Interface Builder.
In summary:
MyModalView: UITableViewController, in Storyboard using Interface Builder has
Extend Edges:
- Under Top Bars ticked
- Under Bottom Bars ticked
- Under Opaque Bars ticked
I found it's the simulated metrics (top bar) in storyboard that's cause this problem.
In my case, the following lines work, but I still don't know why.
- (void)willPresentSearchController:(UISearchController *)searchController {
// do something before the search controller is presented
self.navigationController.navigationBar.translucent = YES;
}
-(void)willDismissSearchController:(UISearchController *)searchController
{
self.navigationController.navigationBar.translucent = NO;
}
I had to
self.aNavigationController?.extendedLayoutIncludesOpaqueBars = true
I found a similar question here but in my case it was not on the viewDidLoad method. I had to try different views until it worked. Now I can have both a custom navigation bar color and the search bar,
Thanks #wiles duan and #Techprimate
In my case, I fixed this issue by setting:
self.definesPresentationContext = NO;
And implement the following 2 methods in UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController {
// do something before the search controller is presented
self.navigationController.navigationBar.translucent = YES;
}
-(void)willDismissSearchController:(UISearchController *)searchController
{
self.navigationController.navigationBar.translucent = NO;
}
I fixed it in my case by removing
definesPresentationContext = true
I didn't test yet if there are any disadvantages of removing this!
I had the same problem, and when I debugged the UI on Xcode I found that the UISearchBar view was moved to another view and the width was zeroed.
I fixed it by setting definesPresentationContext property of the UISearchController to false, and setting it true for the containing UITableViewController.
I added only one line to your viewDidLoad().
override func viewDidLoad() {
super.viewDidLoad()
// Dynamically create a search controller using anonymous function
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.definesPresentationContext = false // Disable the presentation controller
controller.searchBar.sizeToFit()
controller.searchBar.delegate = self
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
// Auto sizing row & cell height
self.tableView.estimatedRowHeight = 130
self.tableView.rowHeight = UITableViewAutomaticDimension
self.definesPresentationContext = true // This one remains the same
// No footer for better presentation
self.tableView.tableFooterView = UIView.init(frame: CGRectZero)
}
I don't have a navigation bar in this place of an app. None of other SO posts helped me, so I've fixed it this way:
- (void)layoutSubviews
{
[[[self searchController] searchBar] sizeToFit];
}
Setting the navigationBar permanently to translucent in storyboard solved my problem.
It works
override func viewDidLoad() {
super.viewDidLoad()
self.extendedLayoutIncludesOpaqueBars = !self.navigationController!.navigationBar.translucent
}
If you want to hide you navigation bar, and present search controller full screen, set the following on your navigation bar and search bar won't dissapper:
navigationController?.navigationBar.translucent = true
sc.hidesNavigationBarDuringPresentation = false
does the trick for me
lazy var searchController:UISearchController = {
let sc = UISearchController(searchResultsController: nil)
sc.searchResultsUpdater = self
sc.obscuresBackgroundDuringPresentation = false
sc.searchBar.placeholder = "Search"
sc.hidesNavigationBarDuringPresentation = false
return sc
}()
None of them worked for me, I fixed it using this hack
func position(for bar: UIBarPositioning) -> UIBarPosition {
if UIDevice.current.userInterfaceIdiom == .pad {
return .top
} else {
if iOSVersion <= 9 {
return .top
}
return .topAttached
}
}

Make UISearchBar dismiss like Music App

Notice how the searchBar magnifying glass and placeholder text both shift over on dismissal:
Is there any way I could dismiss this search bar without the text and icon moving over? See the animation of the Music app for the animation I am trying to achieve.
I have a UISeachBar that gets presented when the search button is clicked in a nav bar.
#IBAction func searchButtonPressed(sender: AnyObject) {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
presentViewController(searchController, animated: true, completion: nil)
}
Now when the cancel button is pressed, I want the whole view to move up without seeing the SearchBar icon and placeholder text slide to the center of the searchBar as the view moves up off screen.
I know there is the method searchBarCancelButtonClicked(searchBar:), but this seems to automatically dismiss the SearchBar and I don't know how to control that.
How can I make the SearchBar dismiss like the one in the Music app?
EDIT: Here is the only delegate method I am using:
func updateSearchResultsForSearchController(searchController: UISearchController) {
masterFilter.removeAll()
filterContentForSearchText(searchController.searchBar.text!)
tableView.reloadData()
}
This is how I have achieved this. It is simple and easy.
extension UIViewController {
func setNavigationBarItem() {
let searchaButton = UIButton.init(frame: CGRectMake(0, 0, 30, 30))
searchaButton.setBackgroundImage(UIImage(named: "search.png"), forState: UIControlState.Normal)
searchaButton.addTarget(self, action: #selector(self.searchPressed), forControlEvents: UIControlEvents.TouchUpInside)
let rightButton: UIBarButtonItem = UIBarButtonItem.init(customView: searchaButton)
navigationItem.rightBarButtonItem = rightButton
}
public func searchPressed(){
var searchController: UISearchController!
// Create the search results view controller and use it for the UISearchController.
let searchResultsController = storyboard!.instantiateViewControllerWithIdentifier("SearchResultController") as! SearchResultsViewController
// Create the search controller and make it perform the results updating.
searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.hidesNavigationBarDuringPresentation = false
// Present the view controller.
presentViewController(searchController, animated: true, completion: nil)
}
}
Note:
This code is for old swift version.
I hope this will help you.
The searchBar expands to the right because the search is being cancelled, and the cancel button is disappearing.
Instead of canceling the search and triggering that animation, simply explicitly dismiss the search controller.
You can do this by setting
searchController.active = false
To make this animation work, you'll need to understand custom UIViewController transitions. Within the UISearchBarDelegate method -searchBarCancelButtonClicked(searchBar:), you'll need to do a few things:
Hide the UINavigationBar on the UISearchController dismissal
Animate the UITableView to the bottom of the frame and fade out.
If you're presenting search modally, ensure that you're setting the modalPresentationStyle to UIModalPresentationCurrentContext.

Cannot set searchBar as firstResponder

I have a searchBar I'm setting in a tableviewcontroller. i've referenced this similar question UISearchBar cannot become first responder after UITableView did re-appear but am still unable to set it as first responder.
In .h file:
#property (strong, nonatomic) UISearchController *searchController;
#property (strong, nonatomic) IBOutlet UISearchBar *searchBar;
In view didload:
self.searchController = [[UISearchController alloc]initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
self.searchController.searchBar.frame = CGRectMake(self.searchController.searchBar.frame.origin.x, self.searchController.searchBar.frame.origin.y, self.searchController.searchBar.frame.size.width, 44.0);
self.tableView.tableHeaderView = self.searchController.searchBar;
And in viewDidAppear:
-(void)viewDidAppear:(BOOL)animated {
[self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
[super viewDidAppear:animated];
}
When I segue to the view the searchBar animates, but no keyboard appears.
I noticed this issue too. What seems to happen is the call to becomeFirstResponder is done when the searchController is still 'loading'. If you comment out becomeFirstResponder you notice that there is no difference. So we need a way to call becomeFirstResponder after the searchController is 'done' loading.
When I looked at various delegate methods I noticed there is a delegate method:
- (void)didPresentSearchController:(UISearchController *)searchController
This method is called right after the searchController has been presented. Then I make the call to becomeFirstResponder:
- (void)didPresentSearchController:(UISearchController *)searchController
{
[searchController.searchBar becomeFirstResponder];
}
This fixes the problem. You will notice that when the searchController is loaded, the searchbar now has focus.
The solution with
- (void)didPresentSearchController:(UISearchController *)searchController did not work, since this delegate method is called only when the user taps on the search bar...
However, this solution did work:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(showKeyboard) withObject:nil afterDelay:0.1];
}
- (void) showKeyboard
{
[self.searchController.searchBar becomeFirstResponder];
}
Swift 3
delay(0.1) { self.searchController.searchBar.becomeFirstResponder() }
func delay(_ delay: Double, closure: #escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
Well, I found the solution that is actually perfectly working for me.
Don't call [self.searchController setActive:YES]; before calling [self.searchController.searchBar becomeFirstResponder];
What's better, don't call [self.searchController setActive:YES]; at all.
Call only [self.searchController.searchBar becomeFirstResponder]; and the keyboard just pops out as it should, without any delay.
It seems to be somewhat like a bug and a lot of people are confirming it. For example, check here: When assigning focus via becomeFirstResponder to UISearchController's UISearchBar, the keyboard does not appear
Swift 4, iOS 11
It works for me
// 1.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
resultSearchController.isActive = true
}
// 2. ->> UISearchControllerDelegate
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchBar.becomeFirstResponder()
}
}
The function searchController.searchBar.becomeFirstResponder() must be called in the main thread and after searchController.active = true in the viewDidLoad method. Here're the full solution. It works on iOS 9.3
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
searchController.active = true
Async.main {
self.searchController.searchBar.becomeFirstResponder()
}
}
Very similar to other answers, but I had to access the main queue in the ViewDidAppear.
The SearchBarController can't be acted upon until the View appears and then can only be done so in the main queue for UI:
searchController.active = true // doubtful whether this is needed
dispatch_async(dispatch_get_main_queue(), {
self.searchController.searchBar.becomeFirstResponder()
});
Easy Swift3 variant:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = mySearchController.searchBar
mySearchController.searchResultsUpdater = self
mySearchController.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.mySearchController.isActive = true
}
}
func presentSearchController(_ searchController: UISearchController) {
mySearchController.searchBar.becomeFirstResponder()
}
It works ;-)
Swift 5 solution:
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true
searchController.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
extension ViewController: UISearchControllerDelegate {
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {[weak self] in
self?.searchController.searchBar.becomeFirstResponder()
}
}
}
Xcode 11.4, Swift 5.2
If you just want the SearchBar to appear but not activate the TextField & keyboard. There is no need to dispatch it to the main thread.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
If you want the TextField to activate with the keyboard, then you do need to call it on the main thread. There is no need to make the SearchController active, this happens automatically.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
This may depend on how you configure your SearchController. This is how I configure mine:
// Defined in class
let searchController = UISearchController(searchResultsController: nil)
// Called in viewDidLoad
navigationItem.searchController = searchController
searchController.searchResultsUpdater = self
searchController.searchBar.scopeButtonTitles = ["A", "B"]
searchController.searchBar.delegate = self
searchController.obscuresBackgroundDuringPresentation = false
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.
This is what it worked for me in Swift 4
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
DispatchQueue.main.async{
self.searchController.searchBar.becomeFirstResponder()
}
}
The answer is to call becomeFirstResponder in viewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchBar?.becomeFirstResponder()
}
Based on #mislovr's solution, the 0.1 delay was not long enough. Here is my updated code to that answer.
func presentSearchController() {
searchController.isActive = true
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak searchController] timer in
guard let searchController = searchController else {
timer.invalidate()
return
}
if searchController.searchBar.canBecomeFirstResponder {
searchController.searchBar.becomeFirstResponder()
timer.invalidate()
}
}
}
i fixed this as follows:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
searchController.searchBar.searchTextField.becomeFirstResponder()
}
}
What happens is as soon as the view appears you set your searchControllers 'isActive' property to true. This fires the delegate method called didPresentSearchController. when that fires it means that searchcontroller is visible and thus can become first responder.
I think there might be a cleaner solution. I found that the keyboard was sort of 'blipping' up and then back down when presented, and calling becomeFirstResponder in didPresentSearchController: was working, but the keyboard was coming in late, and the animation was a bit quirky.
Wrapping my reload data method with a check for presentation made everyone happy:
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
if (!searchController.isBeingPresented && !searchController.isBeingDismissed) {
[self.collectionView reloadData];
}
}
I found this by setting a breakpoint in resignFirstResponder. resignFirstResponder was being called from the reloadData, which in turn was called by updateSearchResultsForSearchController:. It turns out that updateSearchResultsForSearchController: is called during the presentation of the search controller. If you muck with the view hierarchy that the UISearchBar is in during this time, the search controller gets borked. My guess is that the reloadData call was causing the UICollectionReusableView header view to come out and go back into the view hierarchy and forcing the UISearchBar subview to resign first responder.
Another symptom I saw was the the search term was not resetting to the middle of the search bar on cancel, which caused it not to present properly on future clicks.
The solution of mixing #edwardmp and #mislovr answers kind of worked for me (keyboard pops out with a slight delay):
- (void)didPresentSearchController:(UISearchController *)searchController {
[self performSelector:#selector(showKeyboard) withObject:nil afterDelay:0.001];
}
- (void) showKeyboard {
[self.searchController.searchBar becomeFirstResponder];
}
I battled with this for a while but got it working by:
Initializing the searchController in viewDidLoad()
Setting active = true in viewDidAppear()
This triggers didPresentSearchController() in the UISearchControllerDelegate extension.
Setting searchBar.becomeFirstResponder() in didPresentSearchController()
Here's the full example, it uses Google Maps Autocomplete.
class myViewController: UIViewController {
// MARK: Variables
var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
var resultView: UITextView?
// MARK: Outlets
#IBOutlet var myView: UIView!
// MARK: View Methods
override func viewDidLoad() {
super.viewDidLoad()
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.delegate = self
searchController?.searchResultsUpdater = resultsViewController
searchController?.searchBar.prompt = "Search for a Place"
searchController?.searchBar.placeholder = "place name"
searchController?.searchBar.text = ""
searchController?.searchBar.sizeToFit()
searchController?.searchBar.returnKeyType = .Next
searchController?.searchBar.setShowsCancelButton(true, animated: false)
myView.addSubview((searchController?.searchBar)!)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
searchController?.active = true
}
// MARK: GMSAutocompleteResultsViewControllerDelegate Extension
extension myViewController: GMSAutocompleteResultsViewControllerDelegate {
func resultsController(resultsController: GMSAutocompleteResultsViewController,
didAutocompleteWithPlace place: GMSPlace) {
searchController?.active = false
// Do something with the selected place.
print("Place name: ", place.name)
print("Place address: ", place.formattedAddress)
print("Place attributions: ", place.attributions)
}
func resultsController(resultsController: GMSAutocompleteResultsViewController,
didFailAutocompleteWithError error: NSError){
// TODO: handle the error.
print("Error: ", error.description)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictionsForResultsController(resultsController: GMSAutocompleteResultsViewController) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictionsForResultsController(resultsController: GMSAutocompleteResultsViewController) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
extension myViewController: UISearchControllerDelegate {
func didPresentSearchController(searchController: UISearchController) {
self.searchController?.searchBar.becomeFirstResponder()
}
}
}
In Objective C:
- (void)viewDidLoad {
[super viewDidLoad];
// Setup your SearchViewController here...
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Will show Cancel button in White colour
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTintColor:[UIColor whiteColor]];
// searchController.active = YES; // This is not necessary
// set SearchBar first responder inside Main Queue block
dispatch_async(dispatch_get_main_queue(), ^{
[self->searchController.searchBar becomeFirstResponder];
});
}
Swift 5
class ViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Only after viewDidAppear searchController can be activated
searchController?.isActive = true
}
}
extension ViewController: UISearchControllerDelegate {
// didPresentSearchController not work for me
func presentSearchController(_ searchController: UISearchController) {
searchController.searchBar.becomeFirstResponder()
}
}
To me, there’s a quite big lag when using viewDidAppear. It can be better to use becomeFirstResponder asynchronously in viewDidLoad (tested with iOS 10, Swift 3):
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
searchController.searchBar.becomeFirstResponder()
}
}
This is what it worked for me in Swift 3.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.perform(#selector(self.showKeyboard), with: nil, afterDelay: 0.1)
}
func showKeyboard() {
self.searchController.searchBar.becomeFirstResponder()
}
Define searchBar.
SearchBar textfield "searchField" choice.
viewDidLoad or viewWillAppear call code.
#IBOutlet weak var searchBar: UISearchBar!
let textFieldUISearchBar = searchBar.value(forKey: "searchField") as? UITextField
textFieldUISearchBar?.becomeFirstResponder()

Resources