问题
The Problem
I am trying to use UISearchController to search for a destination on a map view. I want the UISearchBar to appear in the navigation bar, but I can't seem to make it do so without it showing a cancel button to the right of it:

This Cancel button has disappeared at times, whilst I'm playing around, but I can't get it to not appear now I have got the search table showing how I want it to:

I'm sure there must be something small I'm doing ever so slightly wrong, but I can't work out what it is...
My Code
self.resultsViewController = [UITableViewController new];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:self.resultsViewController];
self.searchController.searchResultsUpdater = self;
self.searchController.hidesNavigationBarDuringPresentation = false;
self.searchController.delegate = self;
self.searchBar = self.searchController.searchBar;
self.searchBar.placeholder = self.stage.title;
self.searchBar.searchBarStyle = UISearchBarStyleMinimal;
self.definesPresentationContext = true;
self.navigationItem.titleView = self.searchBar;
self.resultsTableView = self.resultsViewController.tableView;
self.resultsTableView.dataSource = self;
self.resultsTableView.delegate = self;
回答1:
Updated in light of comments
UISearchBar
has a property (see the Apple docs) which determines whether the cancel button is displayed:
self.searchBar.showsCancelButton = false;
But, as per OP comments, this does not work, because the searchController keeps switching the cancel button back on. To avoid this, create a subclass of UISearchBar, and override the setShowsCancelButton
methods:
@implementation MySearchBar
-(void)setShowsCancelButton:(BOOL)showsCancelButton {
// Do nothing...
}
-(void)setShowsCancelButton:(BOOL)showsCancelButton animated:(BOOL)animated {
// Do nothing....
}
@end
To ensure this subclass is used by the searchController, we also need to subclass UISearchController
, and override the searchBar
method to return an instance of our subclass. We also need to ensure that the new searchBar activates the searchController - I've chosen to use the UISearchBarDelegate
method textDidChange
for this:
@interface MySearchController () <UISearchBarDelegate> {
UISearchBar *_searchBar;
}
@end
@implementation MySearchController
-(UISearchBar *)searchBar {
if (_searchBar == nil) {
_searchBar = [[MySearchBar alloc] initWithFrame:CGRectZero];
_searchBar.delegate = self;
}
return _searchBar;
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchBar.text length] > 0) {
self.active = true;
} else {
self.active = false;
}
}
@end
Finally, change your code to instantiate this subclass:
self.searchController = [[MySearchController alloc] initWithSearchResultsController:self.resultsViewController];
(You will obviously need to import the relevant header files for these subclasses).
回答2:
There is a way easier way...
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
回答3:
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 videoSearchController: 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
})()
And it works properly and smooth
回答4:
You could do like this:
- (void)willPresentSearchController:(UISearchController *)searchController {
dispatch_async(dispatch_get_main_queue(), ^{
searchController.searchBar.showsCancelButton = NO;
}); }
回答5:
@pbasdf's answer works for the most part, but checking the searchText
length to determine whether the UISearchController
is active can add more work to the user. The corner case would be if the user hits the clear button, or deletes the only character in the search bar. This would set active
to NO
, which would automatically call resignFirstResponder
on the UISearchBar
. The keyboard would disappear and if the user wants to change the text or enter more text, it would require tapping again on the search bar.
Instead, I only 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:
- 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.
- 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 thatsearchText.length == 0
. searchBar:textDidChange:
is invoked beforesearchBarShouldBeginEditing:
, which is why we handled it in this order.- I update the search results every time the text changes, but you may want to move the
[self.searchResultsUpdater updateSearchResultsForSearchController:self];
tosearchBarSearchButtonClicked:
if you only want the search performed after the user presses the Search button.
回答6:
I was able to get the UISearchBar
to behave as desired without subclassing by calling setShowsCancelButton
in a couple of UISearchBarDelegate
methods:
I call it in textDidChange
and searchBarCancelButtonClicked
. Here's what my implementation looks like:
extension MyViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.isEmpty == false {
searchBar.setShowsCancelButton(true, animated: true)
// whatever extra stuff you need to do
} else {
searchBar.setShowsCancelButton(false, animated: true)
// whatever extra stuff you need to do
}
// whatever extra stuff you need to do
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.setShowsCancelButton(false, animated: false)
searchBar.text = nil
searchBar.resignFirstResponder()
tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
// whatever extra stuff you need to do
}
}
来源:https://stackoverflow.com/questions/28396274/uisearchcontroller-disable-cancel-uibarbuttonitem