I have a UISearchController that displays it's searchResultsController (which is another view controller) when the searchbar is tapped. I do this using this UISearchController delegate method:
-(void)presentSearchController:(UISearchController *)searchController {
dispatch_async(dispatch_get_main_queue(), ^{
searchController.searchResultsController.view.hidden = NO;
});
}
However, any time the searchbar's text is empty, whether by manually deleting all text or tapping the little x button, that searchResultsController view is disappearing until I start typing text again. Any ideas why this may be happening? Is there another method or delegate method that is being triggered when searchbar.text is empty?
So after fiddling around with this for a while yesterday, this is the solution I found that ended up working. Figured I'd post it in case anyone else has the same problem!
-(void)presentSearchController:(UISearchController *)searchController {
//forces searchResultsController to appear when searchBar tapped
dispatch_async(dispatch_get_main_queue(), ^{
searchController.searchResultsController.view.hidden = NO;
});
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
//Prevents searchController from disappearing
if ([searchText isEqualToString:#""])
{
[self presentSearchController:self.searchController];
}
}
Related
I'm using a UITableViewController with a UISearchBar. Everything seems to work fine, except I'm getting a strange warning in the textDidChange method that I've never seen before.
This is my code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
[self.searchResults removeAllObjects];
if([searchText isEqualToString:#""]||searchText==nil){
[self.tableView reloadData];
return;
}
for(NSArray *monsterArray in self.monsterArray) {
NSString *name = monsterArray[0];
NSRange r = [[name lowercaseString] rangeOfString:[searchText lowercaseString]];
if(r.location != NSNotFound) {
if(r.location==0) {
[self.searchResults addObject:monsterArray];
}
}
}
[self.tableView reloadData];
}
By stepping through the program, I've found that the warning occurs right before the end of textDidChange. As I mentioned in the title, the warning is this:
There are visible views left after reusing them all: {
(null) = (null);
}
Does anyone know why this is happening, and how to resolve it?
I had a similar issue with a section header view with a custom UITextField. I got rid of the warning by calling resignFirstResponder on the text field before reloading the table view data, and calling becomeFirstResponder after the reload operation. Something like:
// Workaround: hide and show keyboard to prevent warning when reloading results
[self.searchTextField resignFirstResponder];
[self.tableView reloadData];
[self.searchTextField becomeFirstResponder];
While the other answer did get rid of the error being put in the console it also had some unintended consequences. Mainly calling resignFirstResponder and then becomeFirstResponder like that resets the state of the keyboard. So if you type a letter, the keyboard resets to Alpha from Numeric. This becomes annoying if you're trying to type a string of letters.
In my case I found the There are visible views left after reusing them all: { (null) = (null); } error was only logged when I had my UISearchBar set to the TableView's section header. I was doing this to keep the search bar floating at top of a UITableViewController.
Instead I refactored to use a UIViewController, placed the UISearchBar at the top and the UITableView under it this seems to have properly fixed the problem.
I have a UITableView with a UISearchBar on top and have a specific requirement that isn't working.
I have no Cancel button on my UISearchBar (showsCancelButton = NO) so I rely completely on the x within the UISearchBar to cancel the existing search. I rely on the Keyboard's "Search" button to dismiss the keyboard (though it's called Done in my case).
When a user searches in my app, I'm disabling a navigation bar button item because it gives a bad user experience, and only when the search has cancelled does the user get the navigation bar button item back. That's all working.
I have one particular scenario though that I cannot get around.
1) Tap on the Search Bar to enter Text
2) Click DONE on the Keyboard and the Keyboard will disappear
3) With the keyboard resigned, the x remains in the UISearchBar
4) Tap the x in the UISearchBar and the text in the SearchBar disappears and the view refreshes
5) At this point, the navigation bar button should be enabled again, but it's not.
Code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.timelineSearchBar.showsCancelButton = NO;
if ([searchText length] == 0)
{
[self.timelineSearchBar performSelector:#selector(resignFirstResponder)
withObject:nil
afterDelay:0];
}
I know that the code above is meant to dismiss the keyboard when the x is pressed which is fine.
In my case, the keyboard is already resigned, so I want the tapping of the x to just re-enable the navigation bar item.
self.addButton.enabled = YES;
in that if statement above doesn't do anything at all and the navigation bar item is still disabled.
I've even tried in that if statement :
[self.timelineSearchBar performSelector:#selector(enableAdd)
withObject:nil
afterDelay:0];
- (void)enableAdd
{
self.addButton.enabled = YES;
}
but that crashes saying searchBar does not respond to that enableAdd selector.
I've done a breakpoint and see that the if statement above does evaluate to true when I tap the x and it goes into the statement, it "runs" the code to enable the button, but it never happens.
Also my end editing method is:
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
[searchBar setShowsCancelButton:NO animated:YES];
self.addButton.enabled = YES;
}
UPDATE: I've tried the link here http://engineeringtheworld.wordpress.com/2011/04/11/detecting-when-clear-is-clicked-in-uisearchbar-x-button/ with no success - the textField's shouldClear method doesn't get called. I'm using iOS 7 so perhaps there's another way to embed the views with textFields? This is very possibly the right approach, but it's not working with my code because the for statement in that sample never gets evaluated as true (I put in an NSLog).
UPDATE 2: From if the if statement above, I called the searchBarCancelButton method and I had extreme loops being caused, so that of course wasn't the right approach:
[self performSelector:#selector(searchBarCancelButtonClicked:) withObject:self.timelineSearchBar afterDelay: 0];
Any guidance on this would be really appreciated. I know I'm missing a key step but I just can't quite figure it out.
The problem is that when tapping the X button, searchBar:textDidChange: is called before searchBarShouldBeginEditing:
i.e. here is the call flow: searchBar:textDidChange: -> searchBarShouldBeginEditing: -> searchBarTextDidBeginEditing: -> searchBarTextDidEndEditing:
textDidChange is setting enabled to YES, but then shouldBeginEditing is disabling it again. This works perfectly for me:
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
self.addButton.enabled = NO;
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
if(searchBar.text.length == 0){
self.addButton.enabled = YES;
}
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if ([searchText length] == 0)
{
[searchBar performSelector:#selector(resignFirstResponder) withObject:nil afterDelay:0];
}
}
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
self.addButton.enabled = [searchBar.text length] == 0;
[searchBar resignFirstResponder];
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if([searchText length] == 0 && searchBar.text.length == 0)
{
self.addButton.enabled = YES;
[searchBar performSelector: #selector(resignFirstResponder)
withObject: nil
afterDelay: 0.1];
}
}
I'm creating an answer here for clarity, but the answer by Michaels helped massively and is the accepted approach here, although I got my code working.
I did a lot of digging around with NSLogs and interestingly, I discovered that the shouldBeginEditing appeared before the textDidChange (in the order of the NSLogs).
However, I also noticed that in the steps in my original question, with the keyboard dismissed and the UISearchBar containing text and the x visible, if I tapped x, the keyboard didn't appear, but it called shoudlBeginEditing again.
This was the method that was causing the addButton to continue to be greyed out because that method set that button to be disabled.
So I adjusted the code:
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)bar
{
NSLog(#"Should begin");
if ([self.timelineSearchBar.text length] > 0)
{
self.addButton.enabled = NO;
}
else
{
self.addButton.enabled = YES;
}
self.timelineSearchBar.showsCancelButton = NO;
BOOL boolToReturn = self.shouldBeginEditing;
self.shouldBeginEditing = YES;
return boolToReturn;
}
I put the if condition in there so that the code reacted appropriately if I went back to the searchBar after cancelling with the x.
This way, the add button does not disable as soon as I click on the UISearchBar (which brings up the keyboard). However, in the textDidChange (which is what I saw gets called from the very first letter being typed), I put the following condition too:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
NSLog(#"Text did change");
self.timelineSearchBar.showsCancelButton = NO;
if ([self.timelineSearchBar.text length] > 0)
{
self.addButton.enabled = NO;
}
...
}
This way, the UISearchBar would only display the add button when I start typing in a letter. This worked for me because if the user tapped on the UISearchBar and did not type anything in, in the prepareForSegue that went to the view that the Add Entry was calling, I checked if the searchBar isFirstResponder and if so, I resignFirstResponder.
With this in mind, I'm not sure if this is the Apple way of doing things, but it's clean. I'll know soon enough if my app gets rejected because of this, but thanks so much for your help Michaels; you really pointed me in the right direction for an issue that took up my entire afternoon!
I have a small situation that I can't seem to overcome.
I have a UITableView which has a UISearchBar embedded at the top. The TableView is populated by NSFetchedResultsController and CoreData.
The very first time the app is launched, with no data present, if the user tries to search, I've enabled the use of a label that comes up saying "no results found" in the middle of the screen. However, if the user clicks the x in the UISearchBar, it clears the search but doesn't again hide the label.
I don't have a cancel button for the UISearchBar (it's just the x) and I implement the use of hiding a navigation bar button when a search is active.
With this in mind, I've narrowed down the issue to the textDidChange method, but I can't seem to hide the label when the user presses x with and without the keyboard up.
I was hoping someone could potentially shed some light onto this!
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.eventSearchBar.showsCancelButton = NO;
if ([self.eventSearchBar.text length] > 0)
{
self.addButton.enabled = NO;
}
// This code seems to have no effect at all
if ([searchText length] == 0)
{
self.noResultsLabel.hidden = YES;
}
_fetchedResultsController = nil;
NSError *error;
if (![[self fetchedResultsController] performFetch:&error])
{
}
else
{
[self.eventTableView reloadData];
[self.noResultsLabel setHidden:_fetchedResultsController.fetchedObjects.count > 0];
if (![self.eventSearchBar isFirstResponder])
{
self.shouldBeginEditing = NO;
[self.eventTableView reloadData];
[self.eventSearchBar resignFirstResponder];
}
}
}
Update: Two images
Any guidance would really be appreciated!
I've managed to get this working as I desire.
I figured out that the following code in my textDidChange acts when the keyboard is still up and the user presses the x button:
if (![self.eventSearchBar isFirstResponder])
{
self.shouldBeginEditing = NO;
[self.eventTableView reloadData];
[self.eventSearchBar resignFirstResponder];
}
So within here, I put the self.noResultsLabel.hidden = YES;
With wanting to also hide the noResultsLabel if the keyboard is the first responder and the user presses the x, I did the following:
if ([self.eventSearchBar isFirstResponder] && ([self.eventSearchBar.text length] == 0))
{
self.noResultsLabel.hidden = YES;
self.addButton.enabled = YES;
[self.eventTableView reloadData];
}
Now, the user can press the x button in the UISearchBar and it will hide the noResultsLabel if it's showing if the keyboard is or is not the firstResponder.
Thanks for your help everyone.
You just check the condition whether the search text length is 0 or not and if length is 0 then hide that label.
/** called when text changes (including clear) */
(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText1
{
if ([searchText1 length] == 0) {
//Hide The label
}
}
I'm debugging my code, where UISearchBar's delegate method searchBarTextDidBeginEditing: is called exactly twice every time I tap on the search bar (which is in the navigation bar).
The odd thing is that only this delegate method is called twice. The others are called only once within the whole proccess, which is the correct behavior.
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
// called only once
return YES;
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
// called twice every time
[searchBar setShowsCancelButton:YES animated:YES];
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
// called only once
[searchBar setShowsCancelButton:NO animated:YES];
return YES;
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
// called only once
}
Any idea what could be wrong?
The UISeachrBar is set up in Storyboard, with properly connected outlets, although it's not added to any view, and in the particular view controller's viewDidLoad is the following line that adds the search bar to the navigation bar:
self.searchDisplayController.displaysSearchBarInNavigationBar = YES;
I'm using Xcode 5.0.1 and running the code in iOS 7.0.3 Simulator.
I was experiencing the same issue, and dug in a bit deeper.
In my case, I had a subclass of UISearchDisplayController which functioned as UISearchDisplayDelegate for itself, and UISearchBarDelegate for its UISearchBar.
As it turns out, the problem is that UISearchDisplayController implements the following methods that collide with theUISearchBarDelegate` protocol:
- (void)searchBar:(id)arg1 textDidChange:(id)arg2;
- (void)searchBarCancelButtonClicked:(id)arg1;
- (void)searchBarResultsListButtonClicked:(id)arg1;
- (void)searchBarSearchButtonClicked:(id)arg1;
- (void)searchBarTextDidBeginEditing:(id)arg1;
This means if you make a UISearchDisplayController the delegate of its own UISearchBar, those methods will be called twice.
I found that if you unset the searchBar delegate and only keep the searchDisplayController delegate the method is not called at all any more. So the only workaround i could come up with is putting this in the beginning of your searchBarTextDidBeginEditing and searchBarTextDidEndEditing.
static NSDate *lastInvocation;
if ([[NSDate date] timeIntervalSinceDate:lastInvocation] < 0.1f) {
lastInvocation = [NSDate date];
return;
} else {
lastInvocation = [NSDate date];
}
I found this solution:
[searchBar setShowsCancelButton:NO animated:YES];
[searchBar resignFirstResponder];
But funny thing is, I removed it after some two rounds of testing, the code is just called once, not twice
I had problem with searchBarSearchButtonClicked: method been called twice.
Problem was solved by calling [searchBar resignFirstResponder];
#pragma mark - UISearchViewDelegate methods
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
// Do things
[searchBar resignFirstResponder];
}
I have a Controller with a search bar at top, and when the user types, autocompleted search results are shown below it, in a UIScrollView.
Problem:
The results show up, but the user can't scroll those results. The scrollview is frozen. The only way to scroll is to push 'Cancel' in the Search bar. Tapping "Search" hides the keyboard, but even then the scrollview is frozen.
Desired:
As the user is typing, search results are being autocompleted. At any given time, the user can scroll through those results. They should not have to hit Cancel in order to scroll.
There are more results than will fit on the screen so this isn't an issue of content being only slightly larger than the screen.
Nothing special in CancelButtonClicked or SearchButtonClicked:
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
searchBar.text = #"";
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
[searchBar resignFirstResponder];
[NSThread detachNewThreadSelector:#selector(fetchSearchResult:) toTarget:self withObject:searchBar.text];
}
- (void)searchBar:(UISearchBar *)searchBar
textDidChange:(NSString *)searchText {
if([searchBar.text length] >=3){
[mySpinner startAnimating];
[NSThread detachNewThreadSelector:#selector(fetchSearchResult:) toTarget:self withObject:searchBar.text];
}
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
self.searchDisplayController.searchResultsTableView.hidden = YES;
// This occurs when user starts entering text
// We want to keep the background area dark
}
I had possibly the same issue: UISearchBar was nested into UIScrollView, so when search results was shown- It was impossible to scroll parent view.
..I was puzzled but I learned (UISearchDisplayCtrl private api) that UISearchDisplayController locks all parent scroll views (bitch).
So U should add category UISearchDisplayController+Custom:
#implementation UISearchDisplayController (Custom)
- (void)_disableParentScrollViews {
}
- (void)_enableParentScrollViews {
}
#end