For those of you using the UISearchController
in iOS 8 and up, you'll want to simply subclass the UISearchController
. For completeness, you may also want to hide the cancel button, as I did, since clearing the text from the UISearchBar
is effectively a cancel on the search. I've added that code below if you want to use it.
The benefit of this is that you'll be able to use this for any class and any view, rather than requiring a subclass of UIViewController
. I'll even include how I initialize my UISearchController
at the bottom of this solution.
FJSearchBar
This class only needs to be overridden if you want to hide the cancel button as I did. 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 that searchText.length == 0
.
searchBar:textDidChange:
is invoked before searchBarShouldBeginEditing:
, 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];
to searchBarSearchButtonClicked:
if you only want the search performed after the user presses the Search button.