iOS UISearchController: Wait until UISearchController is dismissed after canceling search - ios

I have a UISearchController that is dismissed when the user clicks the cancel button. After the user clicks the cancel button, I want the UISearchController to be dismissed first then the showNewTableData method needs to be called. Here is the code that I am using that works.
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
dispatch_async(dispatch_get_main_queue(), ^{
[self showNewTableData];
});
}
- (void)showNewTableData {
if (self.searchController.active && self.searchController.searchBar.text.length > 0) {
// show search data
} else {
// show non search data
}
}
Using dispatch_async seems to accomplish my requirements very well, but not sure if this is a good idea. If I don't use dispatch_async, I will end up showing search data because the search bar hasn't finished clearing text and it is still active. Any suggestions are appreciated.

Seeing as how you're trying to do a UI change, its fine to do this in the main thread.

Related

Perform action on UISearchBar when Keyboard is down and the x has been tapped

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!

nested push animation... after [tableview scrollToRowAtIndexPath: ] when returning from Add new item

My app has a method to add a new item to a table (using Core Data for storage) which initializes the new object, adds it to Core Data, and segues to an Edit view.
After editing and hitting the default "Back" button in the Edit view, I do the following in ViewDidAppear in order to scroll my table to the newly added item:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// if a new event was just added then scroll the tableview to it
if (_newlyAddedEvent != nil) {
[self.eventListTable scrollToRowAtIndexPath:_newlyAddedEventIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
_newlyAddedEvent = nil;
}
// dismiss the progress HUD
[SVProgressHUD dismiss];
}
The code executes and throws the following two log messages:
nested push animation can result in corrupted navigation bar
and
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
And of course, my navigation bar is all messed up.
_newlyAddedEvent is set in my controllerDidChangeContent method for the NSFetchedResultsController.
_newlyAddedEventIndexPath = newIndexPath; in the NSFetchedResultsController's didChangeObject method when type = NSFetchedResultsChangeInsert.
Can anyone give me a hint what to look for to fix this?
I've tried adding [self.eventListTable setDelaysContentTouches:NO]; in ViewDidLoad and it didn't help.
Thanks!
Put this code in dispatch main queue:
dispatch_async(dispatch_get_main_queue(),
^{
[self.eventListTable scrollToRowAtIndexPath:_newlyAddedEventIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

iOS: show same "Please sign-in" screen for some view controllers, then change it to real content?

I have an app with tabbed view: search | subscribes | messages
All tab buttons are visible at app start even for unauthorized user.
But when unauthorized user clicks on let's say messages tab, I want to show "MessagesViewController" but it must show grey screen:
Please sign-in. [button sign-in] [button registration]
When user clicks [button sign-in] - modal UIViewController appears. After sign-in user goes back to "MessagesViewController" tab, but this time user can see his messages. Same grey screen must be in subscribes tab.
I'm new to iOS so I want to know what is a proper way to do this. Subscribes and messages are table views. Do I need to place another view on top of tables to overlap them and then hide it after authorization? Or can I create one separate "unauthorized" controller, connect it with tabs and reuse it? If so, how can I connect messages and subscribes controllers back to tabs after sign in?
Create a baseViewController, make all three viewController's extend that base.
Think of a method that will show the user "please sign-in" message. A gray UIView (with a UILabel and two UIButtons) overriding all the content seems good to me.
Add the buttons on that gray UIView, with self s as targets
Check in the viewWillAppear method whether the user is not logged in or not. Show the gray view if not.
Create a boolean and before you fire the segue you proof, if the boolean is yes or no. When the boolean is yes, the segue is performed. Otherwise a segue to your Login View is performed.
I hope this solves your problem
you can use
- (void) viewWillAppear:(BOOL)animated
to do this
#interface Messages
{
BOOL loggedIn;
UILabel *pleaseSignIn;
UIButton *signin;
UIButton *register;
}
- (void) viewWillAppear:(BOOL)animated
{
if([[[NSUserDefaults standardUserDefaults] valueForKey:#"logged"] isEqualToString:#"YES"])
{
loggedIn = YES;
}
else
{
loggedIn = NO;
}
if(loggedIn)
{
//Display messages
pleaseSignin.hidden = YES;
signin.hidden = YES;
register.hidden = YES;
}
else
{
//Remove messages view
pleaseSignin.hidden = NO;
signin.hidden = NO;
register.hidden = NO;
}
}

ECSlidingViewController freezes UIRefreshControl

I'm using ECSlidingViewController in my app. I have a View Controller with a UITableView in it, which uses a UIRefreshControl to execute a long task. When I pull down to refresh, the refresh control starts animating normally but when I go to the top view controller (my main menu in this case) and then go back to my view controller, the refreshControl freezes completely until the task is completed.
To make it clearer, these are the steps I follow:
Pull down my tableView to refresh it.
A long task is executed on background, like:
- (void)pulledToRefresh:(UIRefreshControl *)refreshControl
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
long long a = 0;
for (long long i = 0; i < 100000000000; i++) {
a++;
}
dispatch_async(dispatch_get_main_queue(), ^{
[refreshControl endRefreshing];
});
});
}
While the refresh control is animating, I slide (I don't call the resetTopView:animated: method or any of those ones, I just make a pan gesture) my current view controller to the right to show my Main Menu. When this happens, the refresh control suddenly stops animating (it freezes).
I go back to the view controller I was using at first but it remains frozen.
The task completes and the refresh control ends the refresh naturally, removing itself from my tableView with a nice animation.
Any idea why this happens?
I may have found a solution for this.... Changing the "detectPanGestureRecognizer" method to below, seems to be working for me...
- (void)detectPanGestureRecognizer:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
_isInteractive = YES;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.defaultInteractiveTransition updateTopViewHorizontalCenterWithRecognizer:recognizer];
});
_isInteractive = NO;
}

What is the correct usage of the UISearchBarDelegate showsSearchResultsButton attribute?

I configure my search bar to show the results button, but the button only shows until the user enters a character. At that point, the "X" cancel button replaces it. So without entering characters, the search result set equals the entire data set. I'd like the results button to stay there so when the user has typed enough characters to get a smaller result set (like 5 or 6 rows), they can click the results button, my delegate will get called, and I can show just that result set.
UISearchBar * theSearchBar = [[UISearchBar alloc]
initWithFrame:CGRectMake(0,0,700,40)];
theSearchBar.delegate = self;
theSearchBar.placeholder = #"What are you looking for?";
theSearchBar.showsCancelButton = NO; // shows up after first char typed.
theSearchBar.showsSearchResultsButton = YES; // disappears just when I need it.
...further down in the VC... this method can only called when the search bar's input field is empty.
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar {
NSLog(#" searchBarResultsListButtonClicked for %#",searchBar); //
}
Advice, tutorials, sample code and justified dope-slaps welcome.
TIA
-Mike
#Rayfleck, I think you should not worry about Search Results Button at all.
If what you need is to monitor user's input until they have entered enough characters for filtering:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchText length]>5) {
[self filterDataWithKeyword:searchText];
[self.tableView reloadData];
} else {
[self resetFilter];
[self.tableView reloadData];
}
}
Here is a partial answer that you can stick in viewDidLoad. It should hide the clear button, but it doesn't keep the results button visible. I'm not sure how the results button view logic is controlled behind the scenes.
for (id subview in mySearchBar.subviews) {
if ([[subview class] isSubclassOfClass:[UITextField class]]) {
[subview setClearButtonMode:UITextFieldViewModeNever];
break;
}
}
Since this approach uses all public APIs your app shouldn't get rejected. Although this approach might be prone to breaking further down the road if/when Apple decides to change the hierarchy of UISearchBar. All I'm doing is looking for the UITextField or subclass and setting its clearButtonMode.
Hope this helps.

Resources