iOS8 Cannot hide cancel button on search bar in UISearchController
Asked Answered
S

9

22

My goal is to prevent the cancel button from appearing in a search bar in a UISearchController. I started with Apple's Table Search with UISearchController sample code and hid the cancel button as seen in the code snip below. However, when the user taps in the text field, the cancel button still appears. Any help?

override func viewDidLoad() {
    super.viewDidLoad()

    resultsTableController = ResultsTableController()

    searchController = UISearchController(searchResultsController: resultsTableController)
    searchController.searchResultsUpdater = self
    searchController.searchBar.sizeToFit()
    tableView.tableHeaderView = searchController.searchBar

    searchController.searchBar.delegate = self

    //Hide cancel button - added by me
    searchController.searchBar.showsCancelButton = false

    ...
Schnitzler answered 5/11, 2014 at 23:41 Comment(0)
B
21

I think there are three ways of achieving that:

  1. Override searchDisplayControllerDidBeginSearch and use the following code:

searchController.searchBar.showsCancelButton = false

  1. Subclass UISearchBar and override the layoutSubviews to change that var when the system attempts to draw it.

  2. Register for keyboard notification UIKeyboardWillShowNotification and apply the code in point 1.

Of course can always implement your search bar.

Baguio answered 5/11, 2014 at 23:46 Comment(2)
1: is deprecated in iOS 8. It's replacement didPresentSearchController: gets called after the cancel button is already show, causing a flash. 2: I don't think you can tell UISearchController to use a custom subclass of search bar? 3: What about cases where the keyboard isn't shown? For example, if a keyboard is paired/attached?Mccracken
+1 for #1. I'm not getting any warnings about depreciation and I see no flashes, seems to work perfectly and thats the solution with the least amount of code.Delirious
F
17

For iOS 8, and UISearchController, use this delegate method from UISearchControllerDelegate:

func didPresentSearchController(searchController: UISearchController) {
  searchController.searchBar.showsCancelButton = false
}

Don't forget to set yourself as the delegate: searchController.delegate = self

Fetish answered 9/7, 2015 at 16:27 Comment(2)
As @Mccracken pointed out, this works but causes a flash because we hide the cancel button when it's already there.Weekend
@MichaelPirotte this still flashes as of iOS 9.3.1 .Boeke
B
15

Simply subclass UISearchController & UISearchBar.

class NoCancelButtonSearchController: UISearchController {
    let noCancelButtonSearchBar = NoCancelButtonSearchBar()
    override var searchBar: UISearchBar { return noCancelButtonSearchBar }
}

class NoCancelButtonSearchBar: UISearchBar {
    override func setShowsCancelButton(_ showsCancelButton: Bool, animated: Bool) { /* void */ }
}
Blow answered 22/12, 2016 at 1:59 Comment(0)
C
3

The following github project subclasses UISearchBar which is presented as solution 2:

https://github.com/mechaman/CustomSearchControllerSwift

On top of it, it also subclasses UISearchController to enable one to put the search bar in places other than the tableView header!

Hope this helps.

Crispate answered 20/8, 2015 at 11:1 Comment(0)
D
2

This was the simplest solution I could come up with in Swift.

Custom search controller:

class CustomSearchController: UISearchController {

    var _searchBar: CustomSearchBar

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        self._searchBar = CustomSearchBar()
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    override init(searchResultsController: UIViewController?) {
        self._searchBar = CustomSearchBar()
        super.init(searchResultsController: searchResultsController)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var searchBar: UISearchBar {
        return self._searchBar
    }
}

Custom search bar:

class CustomSearchBar: UISearchBar {
    override func setShowsCancelButton(showsCancelButton: Bool, animated: Bool) {
        // do nothing
    }
}

The most important piece of this was to only create the _searchBar object once in init vs. creating it inside of the stored property.

Disconcert answered 7/3, 2016 at 2:13 Comment(0)
G
2

Just subclass your UISearchController and do the following:

class CustomSearchController: UISearchController {

   override func viewDidLayoutSubviews() {
       super.viewDidLayoutSubviews()
       searchBar.showsCancelButton = false
   }
}

This was the easiest solution I could came up with in order to solve the flashing cancel-button issue.

Gasaway answered 12/6, 2017 at 6:32 Comment(1)
It works on iOS 12, but has a little jumping animation. :(Mezereum
O
1

TL;DR: Subclassing UISearchBar and overriding setShowsCancelButton: and setShowsCancelButton:animated: hides the cancel button.

SOLUTION

I set active to NO if the search bar is not the first responder (keyboard is not active and displayed), since that is effectively a cancel command.

FJSearchBar

Marking searchController.searchBar.showsCancelButton = NO doesn't seem to work in iOS 8. I haven't tested iOS 9.

FJSearchBar.h

Empty, but placed here for completeness.

@import UIKit;

@interface FJSearchBar : UISearchBar

@end

FJSearchBar.m

#import "FJSearchBar.h"

@implementation FJSearchBar

- (void)setShowsCancelButton:(BOOL)showsCancelButton {
    // do nothing
}

- (void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated {
    // do nothing
}

@end

FJSearchController

Here's where you want to make the real changes. I split the UISearchBarDelegate into its own category because, IMHO, the categories make the classes cleaner and easier to maintain. If you want to keep the delegate within the main class interface/implementation, you're more than welcome to do so.

FJSearchController.h

@import UIKit;

@interface FJSearchController : UISearchController

@end

@interface FJSearchController (UISearchBarDelegate) <UISearchBarDelegate>

@end

FJSearchController.m

#import "FJSearchController.h"
#import "FJSearchBar.h"

@implementation FJSearchController {
@private
    FJSearchBar *_searchBar;
    BOOL _clearedOutside;
}

- (UISearchBar *)searchBar {
    if (_searchBar == nil) {
        // if you're not hiding the cancel button, simply uncomment the line below and delete the FJSearchBar alloc/init
        // _searchBar = [[UISearchBar alloc] init];
        _searchBar = [[FJSearchBar alloc] init];
        _searchBar.delegate = self;
    }
    return _searchBar;
}

@end

@implementation FJSearchController (UISearchBarDelegate)

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
    // if we cleared from outside then we should not allow any new editing
    BOOL shouldAllowEditing = !_clearedOutside;
    _clearedOutside = NO;
    return shouldAllowEditing;
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    // hide the keyboard since the user will no longer add any more input
    [searchBar resignFirstResponder];
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    if (![searchBar isFirstResponder]) {
        // the user cleared the search while not in typing mode, so we should deactivate searching
        self.active = NO;
        _clearedOutside = YES;
        return;
    }
    // update the search results
    [self.searchResultsUpdater updateSearchResultsForSearchController:self];
}

@end

Some parts to note:

  1. I've put the search bar and the BOOL as private variables instead of properties because
    • They're more lightweight than private properties.
    • They don't need to be seen or modified by the outside world.
  2. We check whether the searchBar is the first responder. If it's not, then we actually deactivate the search controller because the text is empty and we're no longer searching. If you really want to be sure, you can also ensure that searchText.length == 0.
  3. searchBar:textDidChange: is invoked before searchBarShouldBeginEditing:, which is why we handled it in this order.
  4. I update the search results every time the text changes, but you may want to move the [self.searchResultsUpdater updateSearchResultsForSearchController:self]; to searchBarSearchButtonClicked: if you only want the search performed after the user presses the Search button.
Overdose answered 6/3, 2016 at 6:7 Comment(0)
M
1

Swift:

The following worked for me, added under viewDidLoad, because I never wanted that button:

let searchBarStyle = searchBar.value(forKey: "searchField") as? UITextField
searchBarStyle?.clearButtonMode = .never

Make sure to add the ID for the searchBar in the storyboard. enter image description here

Martinsen answered 9/6, 2018 at 17:45 Comment(0)
I
0

Use UISearchControllerDelegate.

func willPresentSearchController(_ searchController: UISearchController) {

        searchController.searchBar.setValue("", forKey:"_cancelButtonText")
    }
Ilowell answered 10/4, 2017 at 4:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.