In my application I am using search functionality using default IOS search bar, If i place some string for search its working fine but after the first search i need to display the entire data Source (original content) My functionality is if the search string is empty it will display the entire data source. My issue is if i make the search string as empty in default search bar, the search button automatically come to hide state. I need to enable the search button even the string is empty.
Actually you can just set searchBar.enablesReturnKeyAutomatically = NO; Tested on iOS 7+
This code display Search Button if you have empty string.
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
[self.searchBar setShowsCancelButton:YES animated:YES];
self.tblView.allowsSelection = NO;
self.tblView.scrollEnabled = NO;
UITextField *searchBarTextField = nil;
for (UIView *subview in self.searchBar.subviews)
{
if ([subview isKindOfClass:[UITextField class]])
{
searchBarTextField = (UITextField *)subview;
break;
}
}
searchBarTextField.enablesReturnKeyAutomatically = NO;
}
Swift 3/ iOS 10
for view1 in searchBar.subviews {
for view2 in view1.subviews {
if let searchBarTextField = view2 as? UITextField {
searchBarTextField.enablesReturnKeyAutomatically = false
break
}
}
}
Use the following code for enable return key with no text
UITextField *searchField = nil;
for (UIView *subview in searchBar.subviews) {
if ([subview isKindOfClass:[UITextField class]]) {
searchField = (UITextField *)subview;
break;
}
}
if (searchField) {
searchField.enablesReturnKeyAutomatically = NO;
}
Maybe is an apple side bug?. Since from the .xib file setting auto-enable return Key does not work as expected.
Just add the following code:
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
searchBar.enablesReturnKeyAutomatically = NO;
return YES;
}
Create a custom view & on that view add one button to remove the keyboard. Add that view when - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar delegate method of UISearchBar.
On that button click resign the keyboard as well as the view which you created for that button. Also if you want to search on that button click then you can do it as well.
Please see image for more clarification.
Its almost same as the accepted answer but if You are working with iOS 7 you will need extra for loop due to some changes in search bar
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
[self.searchBar setShowsCancelButton:YES animated:YES];
UITextField *searchBarTextField = nil;
for (UIView *mainview in self.searchBar.subviews)
{
for (UIView *subview in mainview.subviews) {
if ([subview isKindOfClass:[UITextField class]])
{
searchBarTextField = (UITextField *)subview;
break;
}
}
}
searchBarTextField.enablesReturnKeyAutomatically = NO;
}
Use this it works for ios 6 and 7:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)search
{
UITextField *searchBarTextField = nil;
for (UIView *mainview in search.subviews) {
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
if ([mainview isKindOfClass:[UITextField class]]) {
searchBarTextField = (UITextField *)mainview;
break;
}
}
for (UIView *subview in mainview.subviews) {
if ([subview isKindOfClass:[UITextField class]]) {
searchBarTextField = (UITextField *)subview;
break;
}
}
}
searchBarTextField.enablesReturnKeyAutomatically = NO;
}
If you're looking for a bit more elegant solution, you could use recursion to find the textfield within the searchbar, as shown below. This should work for ios 6, 7, or any other future ios barring any deprecations by apple.
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
UITextField* textField = [self findTextFieldInView:searchBar];
if (textField) {
textField.enablesReturnKeyAutomatically = NO;
}
}
-(UITextField*)findTextFieldInView:(UIView*)view {
if ([view isKindOfClass:[UITextField class]]) {
return (UITextField*)view;
}
for (UIView* subview in view.subviews) {
UITextField* textField = [self findTextFieldInView:subview];
if (textField) {
return textField;
}
}
return nil;
}
Simple Swift version:
if let searchTextField:UITextField = searchBar.subviews[0].subviews[2] as? UITextField {
searchTextField.enablesReturnKeyAutomatically = false
}
Here is a solution using Swift. Just paste it in your viewDidLoad function and make sure that you have an IBOutlet of your searchBar in the code. (on my example below, the inputSearchBar variable is the IBOutlet)
// making the search button available when the search text is empty
var searchBarTextField : UITextField!
for view1 in inputSearchBar.subviews {
for view2 in view1.subviews {
if view2.isKindOfClass(UITextField) {
searchBarTextField = view2 as UITextField
searchBarTextField.enablesReturnKeyAutomatically = false
break
}
}
}
In swift use UISearchBarDelegate
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
searchBar.enablesReturnKeyAutomatically = false
}`
Related
I'm using willTransitionToState which notifies me when the right hand delete button is shown. However, this method is not called when the delete is cancelled by tapping outside the cell area. I've also tried tableView:didEndEditingRowAtIndexPath.
The answers found in this question don't work in iOS 7.
The following code works for iOS 7 (not for iOS 6). The iOS 6 solution is this.
- (void)layoutSubviews
{
[super layoutSubviews];
[self detectDeleteButtonState];
// it takes some time for delete button to disappear
[self performSelector:#selector(detectDeleteButtonState) withObject:self afterDelay:1.0];
}
- (void)detectDeleteButtonState
{
BOOL isDeleteButtonPresent = [self isDeleteButtonPresent:self.subviews];
if (isDeleteButtonPresent) {
NSLog(#"delete button is shown");
} else {
NSLog(#"delete button is gone");
}
}
-(BOOL)isDeleteButtonPresent:(NSArray*)subviews
{
for (UIView *subview in subviews)
{
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellDeleteConfirmationView"])
{
return [subview isHidden] == NO;
}
if([subview.subviews count] > 0){
return [self isDeleteButtonPresent:subview.subviews];
}
}
return NO;
}
In the contacts app on the iPhone if you enter a search term, then tap the "Search" button, the keyboard is hidden, BUT the cancel button is still enabled. In my app the cancel button gets disabled when I call resignFirstResponder.
Anyone know how to hide the keyboard while maintaining the cancel button in an enabled state?
I use the following code:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
}
The keyboard slides out of view, but the "Cancel" button to the right of the search text field is disabled, so that I cannot cancel the search. The contacts app maintains the cancel button in an enabled state.
I think maybe one solution is to dive into the searchBar object and call resignFirstResponder on the actual text field, rather than the search bar itself.
Any input appreciated.
This method worked in iOS7.
- (void)enableCancelButton:(UISearchBar *)searchBar
{
for (UIView *view in searchBar.subviews)
{
for (id subview in view.subviews)
{
if ( [subview isKindOfClass:[UIButton class]] )
{
[subview setEnabled:YES];
NSLog(#"enableCancelButton");
return;
}
}
}
}
(Also be sure to call it anywhere after [_searchBar resignFirstResponder] is used.)
try this
for(id subview in [yourSearchBar subviews])
{
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:YES];
}
}
The accepted solution will not work when you start scrolling the table instead of tapping the "Search" button. In that case the "Cancel" button will be disabled.
This is my solution that re-enables the "Cancel" button every time it is disabled by using KVO.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Search for Cancel button in searchbar, enable it and add key-value observer.
for (id subview in [self.searchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:YES];
[subview addObserver:self forKeyPath:#"enabled" options:NSKeyValueObservingOptionNew context:nil];
}
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Remove observer for the Cancel button in searchBar.
for (id subview in [self.searchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]])
[subview removeObserver:self forKeyPath:#"enabled"];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Re-enable the Cancel button in searchBar.
if ([object isKindOfClass:[UIButton class]] && [keyPath isEqualToString:#"enabled"]) {
UIButton *button = object;
if (!button.enabled)
button.enabled = YES;
}
}
As of iOS 6, the button appears to be a UINavigationButton (private class) instead of a UIButton.
I have tweaked the above example to look like this.
for (UIView *v in searchBar.subviews) {
if ([v isKindOfClass:[UIControl class]]) {
((UIControl *)v).enabled = YES;
}
}
However, this is obviously brittle, since we're mucking around with the internals. It also can enable more than the button, but it works for me until a better solution is found.
We should ask Apple to expose this.
This seemed to work for me (in viewDidLoad):
__unused UISearchDisplayController* searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
I realize I should probably be using the UISearchDisplayController properly, but this was an easy fix for my current implementation.
You can use the runtime API to access the cancel button.
UIButton *btnCancel = [self.searchBar valueForKey:#"_cancelButton"];
[btnCancel setEnabled:YES];
I expanded on what others here already posted by implementing this as a simple category on UISearchBar.
UISearchBar+alwaysEnableCancelButton.h
#import <UIKit/UIKit.h>
#interface UISearchBar (alwaysEnableCancelButton)
#end
UISearchBar+alwaysEnableCancelButton.m
#import "UISearchBar+alwaysEnableCancelButton.h"
#implementation UISearchBar (alwaysEnableCancelButton)
- (BOOL)resignFirstResponder
{
for (UIView *v in self.subviews) {
// Force the cancel button to stay enabled
if ([v isKindOfClass:[UIControl class]]) {
((UIControl *)v).enabled = YES;
}
// Dismiss the keyboard
if ([v isKindOfClass:[UITextField class]]) {
[(UITextField *)v resignFirstResponder];
}
}
return YES;
}
#end
Here's a slightly more robust solution that works on iOS 7. It will recursively traverse all subviews of the search bar to make sure it enables all UIControls (which includes the Cancel button).
- (void)enableControlsInView:(UIView *)view
{
for (id subview in view.subviews) {
if ([subview isKindOfClass:[UIControl class]]) {
[subview setEnabled:YES];
}
[self enableControlsInView:subview];
}
}
Just call this method immediately after you call [self.searchBar resignFirstResponder] like this:
[self enableControlsInView:self.searchBar];
Voila! Cancel button remains enabled.
Till iOS 12, you can use like this:-
if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "_cancelButton") as? UIButton{
cancelButton.isEnabled = true
}
As of iOS 13, if you use like (forKey: "_cancelButton"), so this use of private API is caught and leads to a crash,
unfortunately.
For iOS 13+ & swift 5+
if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
cancelButton.isEnabled = true
}
I found a different approach for making it work in iOS 7.
What I'm trying is something like the Twitter iOS app. If you click on the magnifying glass in the Timelines tab, the UISearchBar appears with the Cancel button activated, the keyboard showing, and the recent searches screen. Scroll the recent searches screen and it hides the keyboard but it keeps the Cancel button activated.
This is my working code:
UIView *searchBarSubview = self.searchBar.subviews[0];
NSArray *subviewCache = [searchBarSubview valueForKeyPath:#"subviewCache"];
if ([subviewCache[2] respondsToSelector:#selector(setEnabled:)]) {
[subviewCache[2] setValue:#YES forKeyPath:#"enabled"];
}
I arrived at this solution by setting a breakpoint at my table view's scrollViewWillBeginDragging:. I looked into my UISearchBar and bared its subviews. It always has just one, which is of type UIView (my variable searchBarSubview).
Then, that UIView holds an NSArray called subviewCache and I noticed that the last element, which is the third, is of type UINavigationButton, not in the public API. So I set out to use key-value coding instead. I checked if the UINavigationButton responds to setEnabled:, and luckily, it does. So I set the property to #YES. Turns out that that UINavigationButton is the Cancel button.
This is bound to break if Apple decides to change the implementation of a UISearchBar's innards, but what the hell. It works for now.
SWIFT version for David Douglas answer (tested on iOS9)
func enableSearchCancelButton(searchBar: UISearchBar){
for view in searchBar.subviews {
for subview in view.subviews {
if let button = subview as? UIButton {
button.enabled = true
}
}
}
}
Most of the posted solutions are not robust, and will let the Cancel button get disabled under various circumstances.
I have attempted to implement a solution that always keeps the Cancel button enabled, even when doing more complicated things with the search bar. This is implemented as a custom UISearchView subclass in Swift 4. It uses the value(forKey:) trick to find both the cancel button and the search text field, and listens for when the search field ends editing and re-enables the cancel button. It also enables the cancel button when switching the showsCancelButton flag.
It contains a couple of assertions to warn you if the internal details of UISearchBar ever change and prevent it from working.
import UIKit
final class CancelSearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
guard let searchField = value(forKey: "_searchField") as? UIControl else {
assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
return
}
searchField.addTarget(self, action: #selector(enableSearchButton), for: .editingDidEnd)
}
override var showsCancelButton: Bool {
didSet { enableSearchButton() }
}
#objc private func enableSearchButton() {
guard showsCancelButton else { return }
guard let cancelButton = value(forKey: "_cancelButton") as? UIControl else {
assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
return
}
cancelButton.isEnabled = true
}
}
Building on smileyborg's answer, just place this in your searchBar delegate:
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
dispatch_async(dispatch_get_main_queue(), ^{
__block __weak void (^weakEnsureCancelButtonRemainsEnabled)(UIView *);
void (^ensureCancelButtonRemainsEnabled)(UIView *);
weakEnsureCancelButtonRemainsEnabled = ensureCancelButtonRemainsEnabled = ^(UIView *view) {
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UIControl class]]) {
[(UIControl *)subview setEnabled:YES];
}
weakEnsureCancelButtonRemainsEnabled(subview);
}
};
ensureCancelButtonRemainsEnabled(searchBar);
});
}
This solution works well on iOS 7 and above.
For iOS 10, Swift 3:
for subView in self.movieSearchBar.subviews {
for view in subView.subviews {
if view.isKind(of:NSClassFromString("UIButton")!) {
let cancelButton = view as! UIButton
cancelButton.isEnabled = true
}
}
}
For iOS 9/10 (tested), Swift 3 (shorter):
searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true })
For iOS 11 and above, Swift 4-5:
extension UISearchBar {
func alwaysShowCancelButton() {
for subview in self.subviews {
for ss in subview.subviews {
if #available(iOS 13.0, *) {
for s in ss.subviews {
self.enableCancel(with: s)
}
}else {
self.enableCancel(with: ss)
}
}
}
}
private func enableCancel(with view:UIView) {
if NSStringFromClass(type(of: view)).contains("UINavigationButton") {
(view as! UIButton).isEnabled = true
}
}
}
UISearchBarDelegate
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.searchBar.resignFirstResponder()
self.searchBar.alwaysShowCancelButton()
}
for (UIView *firstView in searchBar.subviews) {
for(UIView* view in firstView.subviews) {
if([view isKindOfClass:[UIButton class]]) {
UIButton* button = (UIButton*) view;
[button setEnabled:YES];
}
}
}
You can create your CustomSearchBar inheriting from UISearchBar and implement this method:
- (void)layoutSubviews {
[super layoutSubviews];
#try {
UIView *baseView = self.subviews[0];
for (UIView *possibleButton in baseView.subviews)
{
if ([possibleButton respondsToSelector:#selector(setEnabled:)]) {
[(UIControl *)possibleButton setEnabled:YES];
}
}
}
#catch (NSException *exception) {
NSLog(#"ERROR%#",exception);
}
}
A better solution is
[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;
Better & Easy method:
[(UIButton *)[self.searchBar valueForKey:#"_cancelButton"] setEnabled:YES];
Swift 5 & iOS 14
if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
cancelButton.isEnabled = true
}
One alternative that should be slightly more robust against UIKit changes, and doesn't reference anything private by name is to use the appearance proxy to set the tag of the cancel button. i.e., somewhere in setup:
let cancelButtonAppearance = UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self])
cancelButtonAppearance.isEnabled = true
cancelButtonAppearance.tag = -4321
Then we can use the tag, here the magic number -4321 to find the tag:
extension UISearchBar {
var cancelButton: UIControl? {
func recursivelyFindButton(in subviews: [UIView]) -> UIControl? {
for subview in subviews.reversed() {
if let control = subview as? UIControl, control.tag == -4321 {
return control
}
if let button = recursivelyFindButton(in: subview.subviews) {
return button
}
}
return nil
}
return recursivelyFindButton(in: subviews)
}
}
And finally use searchBar.cancelButton?.isEnabled = true whenever the search bar loses focus, such as in the delegate. (Or if you use the custom subclass and call setShowsCancelButton from the delegate, you can override that function to also enable the button whenever it is shown.)
I am using a UISearchBar to match text input against entries in a database and display the matched results to the user in a UITableView, as they type.
All is well, however, I cannot find a way to alter the return key type of the search bar's keyboard. By default it replaces the standard return key with a Search button. Because I am doing a live search as the user types, I do not need this button and having it there and inactive has raised some usability issues.
Attempted solutions
I can set a keyboard with the setKeyboard:UIKeyboardType method, however this doesn't seem to override the default setting of replacing the return key (on the standard keyboard) with a Search key and it does not allow access to change this return key.
I have thought about using a UITextField, giving me access to the returnKeyType property through the UITextInputTraits protocol. My problem with this however is that I am implementing the UISearchBarDelegate method searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText, which I would lose with the UITextField.
Is there a way that I can keep the functionality of the search bar's delegate methods, whilst having legitimate access to the keyboard's return key?
In fact, almost the exact screen I am implementing is found in Apple's Clock application
Screenshot:
So any help on a clean solution would be much appreciated. Note the return key on the bottom right instead of the default Search button'.
Slightly different in iOS 7 compared to the answer of #sudip.
for (UIView *subview in self.searchBar.subviews)
{
for (UIView *subSubview in subview.subviews)
{
if ([subSubview conformsToProtocol:#protocol(UITextInputTraits)])
{
UITextField *textField = (UITextField *)subSubview;
[textField setKeyboardAppearance: UIKeyboardAppearanceAlert];
textField.returnKeyType = UIReturnKeyDone;
break;
}
}
}
I tried all of these solutions without luck until I realized that in IOS8, you can just set searchBar.returnKey = .Done or whatever UIReturnKeyType you like. Sigh.
Try this:
for(UIView *subView in searchBar.subviews) {
if([subView conformsToProtocol:#protocol(UITextInputTraits)]) {
[(UITextField *)subView setKeyboardAppearance: UIKeyboardAppearanceAlert];
}
}
If you want to dismiss the return key (i.e., make it do nothing), set the "returnKeyType" property on the UITextField subview to "UIReturnKeyDone" along with "keyboardAppearence".
I had to add some lines to Neo's answer. Here is my code to add a "Done" button for UISearchbar :
for(UIView *subView in sb_manSearch.subviews) {
if([subView conformsToProtocol:#protocol(UITextInputTraits)]) {
UITextField *t = (UITextField *)subView;
[t setKeyboardAppearance: UIKeyboardAppearanceAlert];
t.returnKeyType = UIReturnKeyDone;
t.delegate = self;
break;
}
}
To change Search into Done text.
Use below code.
youtSearchBar.returnKeyType = .done
you can do it by :
- (void)viewDidLoad
{
// Adding observer that will tell you keyboard is appeared.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification object:nil];
[super viewDidLoad];
}
- (void)keyboardDidShow:(NSNotification *)note
{
keyboardTest = [self getKeyboard];
[keyboardTest setReturnKeyEnabled: YES];
}
- (id) getKeyboard // Method that returns appeared keyboard's reference
{
id keyboardView;
// locate keyboard view
UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
UIView* keyboard;
for(int i=0; i<[tempWindow.subviews count]; i++)
{
keyboard = [tempWindow.subviews objectAtIndex:i];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2)
{
if([[keyboard description] hasPrefix:#"<UIPeripheralHost"] == YES)
{
keyboard = [[keyboard subviews] objectAtIndex:0];
keyboardView = keyboard ;
}
}
else
{
if([[keyboard description] hasPrefix:#"<UIKeyboard"] == YES)
keyboardView = keyboard ;
}
}
return keyboardView ;
}
UPDATE : From iOS 7 onwards, the accepted answer will not work, below version will the work on iOS 7 onwards.
UIView *subViews = [[_searchBar subviews] firstObject];
for(UIView *subView in [subViews subviews]) {
if([subView conformsToProtocol:#protocol(UITextInputTraits)]) {
[(UITextField *)subView setEnablesReturnKeyAutomatically:NO];
}
}
for (UIView *subView in view.subviews) {
if ([subView isKindOfClass:[UITextField class]])
{
UITextField *txt = (UITextField *)subView;
#try {
[txt setReturnKeyType:UIReturnKeyDone];
[txt setKeyboardAppearance:UIKeyboardAppearanceAlert];
}
#catch (NSException * e) {
// ignore exception
}
}
}
I just found the simplest wait to hack this, just put a blank when beginning editing search field
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar{
//Add a blank character to hack search button enable
searchBar.text = #" ";}
Right, to begin my question, here's some screenies of the problem already solved by the Spotify app:
Spotify's Step 1: Standard UISearchBar not in editing mode.
Spotify's Step 2: UISearchBar now in editing mode. Search term entered. Cancel button slides in from the right, and the clear button (grey x) appears.
Spotify's Step 3: Cancel button pressed; keyboard slides out and the search bar is no longer in editing mode. Search term remains and the grey x button is now hidden.
At present, the following code fires off when my cancel button is pressed:
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
[searchBar setShowsCancelButton:NO animated:YES];
}
Which results in:
My Step 3: Search bar now not in editing mode. Cancel button and keyboard has slid out. Search term remains but so does the grey x.
So, my question is this: given that -resignFirstResponder (and -endEditing:, FYI) does not hide the grey x button when a search bar has had text entered into it, how does one hide it?
Thanks again, friends.
The problem is that UISearchBar doesn't expose it's text field, and manages the properties on the text field itself. Sometimes, the values of the properties aren't what you want.
For instance, in my own app, I wanted the keyboard style for my search bar to use the transparent alert style.
My solution was to walk through the subviews of the search bar until you find the text field. You should then be able to set the clearButtonMode property, using something like UITextFieldViewModeWhileEditing as a parameter.
This should make it so that the clear button is only shown while the text field is editing.
You want to do this on viewDidLoad or something early, so it's set before you start using it (but after the search bar is initialised.
for (UIView *subview in searchBar.subviews)
{
if ([subview conformsToProtocol:#protocol(UITextInputTraits)])
{
[(UITextField *)subview setClearButtonMode:UITextFieldViewModeWhileEditing];
}
}
Looks like iOS 7 changed the view hierarchy of UISearchBar, and the text box is deeper in the view (The above solution didn't work for me). However, modifying the above solution to traverse the whole hierarchy works:
[self configureSearchBarView:[self searchBar]];
- (void)configureSearchBarView:(UIView*)view {
for (UIView *subview in [view subviews]){
[self configureSearchBarView:subview];
}
if ([view conformsToProtocol:#protocol(UITextInputTraits)]) {
[(UITextField *)view setClearButtonMode:UITextFieldViewModeWhileEditing];
}
}
I'm building upon the previous answers because I started seeing crashes on iOS 7.1 unless I made the following change. I added an additional call to respondsToSelector for each view to make sure that setClearButtonMode: can be called. I observed an instance of UISearchBar getting passed in, which seems to conform to the UITextInputTraits protocol yet does not have the setClearButtonMode: selector, so a crash occurred. An instance of UISearchBarTextField also gets passed in and is the actual object for which to call setClearButtonMode:.
- (void)removeClearButtonFromView:(UIView *)view
{
if (!view)
{
return;
}
for (UIView *subview in view.subviews)
{
[self removeClearButtonFromView:subview];
}
if ([view conformsToProtocol:#protocol(UITextInputTraits)])
{
UITextField *textView = (UITextField *)view;
if ([textView respondsToSelector:#selector(setClearButtonMode:)])
{
[textView setClearButtonMode:UITextFieldViewModeNever];
}
}
}
You need to get the textField of the Search Bar
UITextField *textField = [searchBar valueForKey:#"_searchField"];
textField.clearButtonMode = UITextFieldViewModeNever;
use in - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar method.
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
UITextField *textField = [searchBar valueForKey:#"_searchField"];
textField.clearButtonMode = UITextFieldViewModeNever;
}
A better way to do this in iOS7 is:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setClearButtonMode:UITextFieldViewModeWhileEditing];
To expand on Jadariens answer if you never want the grey x to appear you need to use the following
for (UIView *subview in searchBar.subviews)
{
if ([subview conformsToProtocol:#protocol(UITextInputTraits)])
{
[(UITextField *)subview setClearButtonMode:UITextFieldViewModeNever];
}
}
Accepted answer does not work on iOS7+, here is the modified version as a Swift extension
extension UIView {
class func removeClearButton(svs: [UIView]) {
for sv in svs {
if let tv = sv as? UITextField where sv.conformsToProtocol(UITextInputTraits) {
tv.clearButtonMode = .Never
return
} else {
UIView.removeClearButton(sv.subviews)
}
}
}
}
Usage
UIView.removeClearButton(searchBar.subviews)
Hers is a category I wrote that does this
Category
#implementation UISearchBar (Additions)
- (void)setClearButtonMode:(UITextFieldViewMode)viewMode {
UITextField *textField = [self findTextFieldInView:self];
[textField setClearButtonMode:viewMode];
}
- (UITextField *)findTextFieldInView:(UIView *)view {
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UITextField class]] ||
[subview.class isSubclassOfClass:[UITextField class]]) {
return (UITextField *)subview;
}
UITextField *textField = [self findTextFieldInView:subview];
if (textField) {
return textField;
}
}
return nil;
}
#end
Usage
[searchBar setClearButtonMode:UITextFieldViewModeWhileEditing];
There's a better way than any of the answers here, and you don't have to use private APIs or traverse subviews to do it.
UISearchBar has a built-in API for doing this:
[UISearchBar setImage:forSearchBarIcon:state]
The SearchBar icon key you want is UISearchBarIconClear, and you want the UIControlStateNormal state. Then give it a clear image for the image, and you're done.
So, it should look like this:
[searchBar setImage:clearImage forSearchBarIcon:UISearchBarIconClear state:UIControlStateNormal];
For the (x) icon in searchBar. You can use below delegate method.
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
searchBar.showsCancelButton = YES;
}
for (UIView *subview in _search_bar.subviews)
{
NSLog(#"%#",subview.subviews);
for (UIView *subview11 in subview.subviews)
{
if ([subview11 conformsToProtocol:#protocol(UITextInputTraits)])
{
[(UITextField *)subview11 setClearButtonMode:UITextFieldViewModeNever];
}
}
}
Usually, a UISearchDisplayController, when activated, dims the tableView and focuses the searchBar. As soon as you enter text into the searchBar, it creates a searchResultsTableView that displays between the searchBar and the keyboard. The searchDisplayController's delegate gets called when this second UITableView is loaded/shown/hidden/unloaded. Usually it shows live search results or autocompletion entries while typing.
In my app, I want to search a webservice and I don't want to call the webservice for each letter the user enters. Therefore, I want to entirely disable the searchResultsTableView and keep the dimmed black overlay while he enters text. I would then trigger the search (with a loading screen) once he hits the search button.
Just returning zero rows for the searchResultsTableView doesn't look nice since it displays an empty searchResultsTableView with a "no results" message. I tried to hide the table when it appears (searchDisplayController:didLoadSearchResultsTableView:) which works, but the blacked dimmed overlay is also hidden so that the underlying tableView is completely visible again.
Any ideas besides recreating the UISearchDisplayController functionality from scratch?
here is a little trick that i just figured out
and also you have to return 0 results while editing searchstring
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
savedSearchTerm = searchString;
[controller.searchResultsTableView setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.8]];
[controller.searchResultsTableView setRowHeight:800];
[controller.searchResultsTableView setScrollEnabled:NO];
return NO;
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
// undo the changes above to prevent artefacts reported below by mclin
}
i think you'll figure out what to do next
Have you tried this:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(_lookup:) object:nil];
[self performSelector:#selector(_lookup:) withObject:txt afterDelay:0.20];
This way, if the user types another char within 1/5sec, you only make one web call.
Nothing of the above seemed to work well in the end, so I came up with the following (you have to call removeTableHeader when you are ready to display your results):
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar {
[self setTableHeader];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[self setTableHeader];
}
- (void)setTableHeader {
UIView *headerView = [[UIView alloc] initWithFrame:self.searchDisplayController.searchResultsTableView.frame];
headerView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.8];
[self.searchDisplayController.searchResultsTableView setBackgroundColor:[UIColor clearColor]];
[self.searchDisplayController.searchResultsTableView setScrollEnabled:NO];
[self.searchDisplayController.searchResultsTableView setTableHeaderView:headerView];
[headerView release];
}
- (void)removeTableHeader {
[self.searchDisplayController.searchResultsTableView setBackgroundColor:[UIColor whiteColor]];
[self.searchDisplayController.searchResultsTableView setScrollEnabled:YES];
[self.searchDisplayController.searchResultsTableView setTableHeaderView:nil];
}
Obviously, it make the table transparent, adds a black/translucent table header with the same size as the table, and disables scrolling on the table so you cannot get above or past the header. As a bonus, you could add something to the header view ('please wait...' or an activity indicator).
Had the same problem as you, I handled it by a) setting the alpha of the searchResultsTableView to 0 when beginning searching, and then by b) adding/removing the overlayView to the viewController's view. Works like a charm for me.
#interface MyViewController()
//...
#property(nonatomic, retain) UIView *overlayView;
//...
#end
#implementation MyViewController
#synthesize overlayView = _overlayView;
//...
- (void)viewDidLoad
{
//...
//define your overlayView
_overlayView = [[UIView alloc] initWithFrame:CGRectMake(0, 44, 320, 480)];
_overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8];
}
//hide the searchResultsTableView
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
self.searchDisplayController.searchResultsTableView.alpha = 0.0f;
}
//when ending the search, hide the overlayView
- (void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
[_overlayView removeFromSuperview];
}
//depending on what the user has inputed, add or remove the overlayView to the view of the current viewController
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
if ([searchString length]>0)
{
[self.view addSubview:_overlayView];
}
else
{
[_overlayView removeFromSuperview];
}
return NO;
}
#end
it should be sufficient to implement the following method in your UISearchDisplayDelegate (which usually is your custom UITableViewController subclass)
- (BOOL) searchDisplayController: (UISearchDisplayController *) controller shouldReloadTableForSearchString: (NSString *) searchString
{
[self startMyCustomWebserviceSearchAsBackgroundProcessForString: searchString]; //starts new NSThread
return NO;
}
have you tried this?
Based on user182820's code below is my version. I hide the UISearchDisplayController's table view. When a character is entered in the search box I place a 'dimmed view' so it looks like UISearchDisplayController's 'dimmed view' never went away and then remove it when the search is finished. If you enter some characters and press cancel, the table view briefly goes all white and I don't know how to get around this.
- (void)viewDidLoad {
...
tableViewMask=[UIView new];
tableViewMask.backgroundColor = [UIColor blackColor];
tableViewMask.alpha = 0.8;
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller{
tableViewMask.frame=CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y+controller.searchBar.frame.size.height, self.tableView.frame.size.width, self.tableView.frame.size.height-controller.searchBar.frame.size.height);
controller.searchResultsTableView.hidden=YES;
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller{
[tableViewMask removeFromSuperview];
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
if (searchString.length==0)
[tableViewMask removeFromSuperview];
else
[self.tableView addSubview:tableViewMask];
[searchText autorelease];
searchText=[searchString retain];
return NO;
}
What about just doing it as simple as this:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
self.searchDisplayController.searchResultsTableView.hidden=YES;
return YES;
}
Works fine for me..
I foud a better way, since there is a bug with the "best answer" - the separator and the "No Results" will be shown when scrolling the black background table.
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
controller.searchResultsTableView.backgroundColor = [UIColor blackColor];
controller.searchResultsTableView.alpha = 0.8;
controller.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
for(UIView *subview in tableView.subviews) {
if([subview isKindOfClass:UILabel.class]) {
subview.hidden = YES;
}
}
return NO;
}
- (void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {
self.searchDisplayController.searchResultsTableView.backgroundColor = [UIColor whiteColor];
self.searchDisplayController.searchResultsTableView.alpha = 1;
self.searchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
for(UIView *subview in tableView.subviews) {
if([subview isKindOfClass:UILabel.class]) {
subview.hidden = NO;
}
}
// search results and reload data ....
}
All of the existing answers are overly complicated. You can get away by just hiding the results table view immediately.
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView
{
tableView.hidden = YES;
}
I think I found a better implementation for this problem. All the previous answers correctly show a dimmed view identical to what the UITableView looks like before a search, but each solution lacks the functionality to tap the area in order to cancel the search.
For that reason I think this code works better.
First of all, create a BOOL such as searchButtonTapped to indicate whether the search button was tapped. By default it is NO.
Then:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
if (!searchButtonTapped) {
// To prevent results from being shown and to show an identical look to how the tableview looks before a search
[controller.searchResultsTableView setBackgroundColor:[UIColor clearColor]];
[controller.searchResultsTableView setRowHeight:160];
self.searchDisplayController.searchResultsTableView.scrollEnabled = NO;
} else {
// Restore original settings
[controller.searchResultsTableView setBackgroundColor:[UIColor whiteColor]];
[controller.searchResultsTableView setRowHeight:44];
self.searchDisplayController.searchResultsTableView.scrollEnabled = YES;
}
return YES;
}
This should be clear now based on the other answers. Make sure to also restore the original settings when the user taps on the Search button.
Furthermore, in the cellForIndexPath method add:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.contentView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
In order to create the same dimmed view that is shown before text is entered. Make sure you apply these properties to the right cell, i.e., check which UITableView is active and that the user has not tapped the Search button.
Then, crucially, in didSelectRowAtIndexPath:
if (tableView == self.searchDisplayController.searchResultsTableView) {
if (searchButtonTapped) {
// Code for when the user select a row after actually having performed a search
{
else
[self.searchDisplayController setActive:NO animated:YES];
Now the user can tap the dimmed area, which will not result in a visible selection of a UITableViewCell, but instead cancels the search.