I have a UITableViewController that I have specified as a UISearchBarDelegate. Up until now, I had programmatically added the UISearchBar to the headerView of the table, and there were no problems.
I began to run out of screen real estate, so I decided to kill my normal UINavigationController title (which was text), and added the following code, moving my SearchBar from the table to the UINavigationBar:
// (Called in viewDidLoad)
// Programmatically make UISearchBar
UISearchBar *tmpSearchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0,0,320,45)];
tmpSearchBar.delegate = self;
tmpSearchBar.showsCancelButton = YES;
tmpSearchBar.autocorrectionType = UITextAutocorrectionTypeNo;
tmpSearchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
[self set_searchBar:tmpSearchBar];
[tmpSearchBar release];
self.navigationItem.titleView = [self _searchBar];
This code works as expected - my UINavigationBar is now a UISearchBar.
However, my delegate method:
/** Only show the cancel button when the keyboard is displayed */
- (void) searchBarDidBeginEditing:(UISearchBar*) lclSearchBar
{
lclSearchBar.showsCancelButton = YES;
}
...is no longer being called. I've breakpointed, and I've confirmed that the UISearchBar's delegate is indeed self, the view controller. Oddly, this delegate method is still called just fine:
/** Run the search and resign the keyboard */
- (void) searchBarSearchButtonClicked:(UISearchBar *)lclSearchBar
{
_deepSearchRan = NO;
[self runSearchForString:[[self _searchBar] text] isSlowSearch:NO];
[lclSearchBar resignFirstResponder];
}
Any ideas why UINavigationBar is swallowing my delegate calls?? What am I missing?
I think you write the wrong method signature. It should be : – searchBarTextDidBeginEditing:
Here is all the UISearchBarDelegate methods for text editing.
– searchBar:textDidChange:
– searchBar:shouldChangeTextInRange:replacementText:
– searchBarShouldBeginEditing:
– searchBarTextDidBeginEditing:
– searchBarShouldEndEditing:
– searchBarTextDidEndEditing:
UISearchBarDelegate
Related
I have a UITableView in a UINavigationController that contains a rightBarButtonItem which should hide the UITableView and show an MKMapView instead. The button seems to work great: it hides the UITableView, and shows the MKMapView. However, this MKMapView is empty. As in, completely white. I've tried to use a UILabel (just for testing purposes), and that doesn't appear either, so the problem must occur when I add the MKMapView (and UILabel) to the view hierarchy. Some relevant code:
- (void)viewDidLoad {
[super viewDidLoad];
self.mapView = [[MKMapView alloc] init];
mapView.hidden = YES;
[self.view addSubview:self.mapView];
// Some other stuff, table set up, etc.
}
That is the viewDidLoad of a class that inherits from UITableViewController. Now, I use the following method that gets called when tapped on the rightBarButtonItem of the UINavigationController:
- (void) toggleView {
if (self.mapView.isHidden) {
self.mapView.hidden = NO;
self.tableView.hidden = YES;
self.viewButton.title = #"List";
}
else {
self.mapView.hidden = YES;
self.tableView.hidden = NO;
self.viewButton.title = #"Map";
}
}
I am certain that function gets called, I have checked using NSLog. Also, the UITableView correctly disappears, and, I assume, the MKMapView (or whatever other UIView object for that matter) appears, but is empty/completely white. Does anybody see why I'm not seeing maps when trying to switch to Map View?
You should give it a size and position.
CGSize size = self.view.frame.size;
self.mapView =[[MKMapView alloc] initWithFrame:CGRectMake(0,0, size.width, size.height)];
To check the size in the console, add the following line in your toogleview method:
NSLog(#"%#", self.mapView);
I have a UIViewController that is a UISearchBarDelegate and a MKMapViewDelegate. The searchBarSearchButtonClicked event works fine, but when testing in iOS 4.2 the searchBarCancelButtonClicked never gets called when hitting the cancel button. In 4.3 everything works fine. I have other views with identical code and it works fine. I have triple checked the method signatures.
Could it be something to do with the MapView, or am I doing something blatantly wrong?
My .h file:
#interface MyViewController : UIViewController <UISearchBarDelegate,MKMapViewDelegate,UIAlertViewDelegate>{
MKMapView *mapMainView;
UISearchBar *sBar;
}
#property (nonatomic, retain) UISearchBar *sBar;
#end
And I create the search bar like so:
sBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320.0, 70.0)] autorelease];
sBar.delegate = self;
sBar.showsCancelButton = YES;
[self.view addSubview:sBar];
[sBar becomeFirstResponder];
The method:
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
searchBar.hidden = YES;
}
Does anyone have an idea of why this may be happening?
I had the exact same problem. Holding the cancel button for a few seconds worked.
The reason for me was that I had implemented UITapGestureRecognizer in the tableview. So this took precedence over the button click or 'x' button click in the search bar.
The solution in my case was to restrict the gesture recognition to only the backgroundview of the tableview. I guess similar thing might be happening in your case. Try to restrict the gesture recognizers to the minimum subview required and the search bar should be outside that view.
Probably your sbar object are releasing, in this case is an autorelease object, Why ?. Try declaring sBar as IBOutlet property. Make the apropiate links in the Interface Builder, remove the alloc as you code it, put in viewDidUnload
self.sbar = nil;
and releas it in dealloc. in viewDidLoad put this.
sBar.delegate = self;
sBar.showsCancelButton = YES; // this is an option in object inspector
[self.view addSubview:sBar];
[sBar becomeFirstResponder]; //remove this.
Tell me if it works
try this:
sBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, 320.0, 70.0)];
sBar.delegate = self;
sBar.showsCancelButton = YES;
[self.view addSubview:sBar];
and try to put release in dealloc
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.searchDisplayController.searchResultsTableView)
{
[self.searchDisplayController setActive:NO animated:YES];
[self.searchDisplayController.searchBar resignFirstResponder];
}
}
I'm trying to recreate this UISearchBar (as seen in the Table Search example code):
alt text http://img168.imageshack.us/img168/6378/43558113.png
All the examples I've seen to do this involve using a xib, however I need to do it programmatically. The problem is changing the tint color also changes the cancel button's tint:
alt text http://img243.imageshack.us/img243/1375/screenshot20100527at944.png
Any ideas?
Associating the search bar with a UISearchDisplayController magically provides a lot of standard look and behavior such as:
gray tint without affecting cancel button
auto showing/hiding of cancel button
width adjustment around any tableview indexes
In my tableview controller I've done the following:
- (void)viewDidLoad {
[super viewDidLoad];
// setup searchBar and searchDisplayController
UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectZero];
[searchBar sizeToFit];
searchBar.delegate = self;
searchBar.placeholder = #"Search";
self.tableView.tableHeaderView = searchBar;
UISearchDisplayController *searchDC = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
// The above assigns self.searchDisplayController, but without retaining.
// Force the read-only property to be set and retained.
[self performSelector:#selector(setSearchDisplayController:) withObject:searchDC];
searchDC.delegate = self;
searchDC.searchResultsDataSource = self;
searchDC.searchResultsDelegate = self;
[searchBar release];
[searchDC release];
}
I totally agree with Scott McCammon.
However using a performSelector:withObject: on setSearchDisplayController: would not be my approach. This depends on private API which can change at any moment. If Apple would remove their private implementation your app will crash.
A better way would be to override the searchDisplayController: in your view controller to return your instance of UISearchDisplayController:
- (UISearchDisplayControlelr *) searchDisplayController {
return yourInstanceOfASearchController;
}
I don't understand the need for the call to setSearchDisplayController: or the override for searchDisplayController. Under iOS 4.3.2 initWithSearchBar:contentsController: appears to set searchDisplayController for the UIViewController instance passed as the contentsController argument. Perhaps this was a problem in earlier iOS releases, but it appears redundant in the current release.
Does anyone know how to cancel (resign First Responder) out of a UISearchBar when you tap below the search text box and above the keyboard? Can anyone help post some code to handle this?
Thanks
Add a tap gesture in the parent view (of the UISearchbar)
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:searchBar action:#selector(resignFirstResponder)]];
I accomplished this by using a UITapGestureRecognizer:
UIGestureRecognizer* cancelGesture;
- (void) backgroundTouched:(id)sender {
[self.view endEditing:YES];
}
#pragma mark - UISearchBarDelegate
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouched:)];
[self.view addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[self.view removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
The code is a bare, but you can see the intent. When the SearchBar starts editing, you attach a tap gesture recognizer to the view controller's view, and remove it when it stops editing.
There are a couple caveats that you can work around: doing this will make it so if you click anything besides the keyboard or the search bar's text field, the recognizer traps the click -- so if you use the clear, cancel, scope or results button they won't respond correctly.
In my particular scenario, I had a UITableView that was covering the exposed area of the view so I attached the gesture recognizer to it instead of the view controllers main view, isolating the area to which the gesture would respond.
An alternative idea I got from iphonedevbook, sample code project 04, was to use one big transparent button that lies behind all other controls which does nothing but resign all first responders if tapped. I.e. if the user taps anywhere where there isn't a more important control - which is the intuitive behavior - the search bar and keyboard disappear.
I ended up using a hybrid of Hauke's and Beau Scott's approach. There were two problems I ran into using their solutions:
1) If there's anything else on the screen, tapping it won't result in resignFirstResponder being called. For example, if the user taps a button rather than the space around the button, the button will eat the event. Beau Scott's solution addresses this issue, however.
2) Tapping the search bar itself will result in resignFirstResponder getting called. Clearly you don't want the keyboard to disappear when you tap UISearchBar. A small change described below addresses this.
I ended up setting up my view as follows. The parent view has two children - the UISearchBar and a subview which holds the rest of my UI elements. The subview takes up the entire screen below the UISearchBar. Then I used Beau Scott's exact code to add and remove the gesture recognizer, but instead of adding it to self.view I added it to the subview:
IBOutlet UIView *gestureRecognizer;
...
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouch:)];
[gestureRecognizer addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[gestureRecognizer removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
First, you need a reference to the search bar. Let's assume that your controller object has an object reference UISearchBar *theSearchBar, and that you assign it when you create the UISearchBar object.
Next, you need to detect that the containing view has been touched. The view that is touched "knows", but you need get that information to the controller. Sadly, Apple didn't provide a simple way to do this, but it's not that hard either.
My solution is to replace the standard UIView that a UIViewController object normally creates with a UIControl, and then make the UIViewController respond to touch events.
MainController.m
- (void) loadView {
UIControl *control = [[UIControl alloc] initWithFrame: <desired frame>];
[control addTarget: self action: #selector(touchUpInside)
forControlEvents: UIControlEventTouchUpInside];
// or touch down events, or whatever you like
self.view = control;
[control release];
}
- (void) viewDidLoad {
[super viewDidLoad];
theSearchBar = [[UISearchBar alloc] initWithFrame: <desired frame>];
// insert code to finish customizing the search bar
[self.view addSubview: theSearchBar];
}
- (void) touchUpInside {
if [theSearchBar isFirstResponder] {
// grab any data you need from the search bar
[theSearchBar resignFirstResponder];
}
}
MainController.h
#interface MainController : UIViewController
{
UISearchBar *theSearchBar;
}
Clarification:
There is only a single object -- let's call the class MainController -- which is a subclass of UIViewController. All of the methods listed above are implemented in MainController. theSearchBar is declared as a UISearchBar* in the .h file.
Are you defining your view and controller using Interface Builder? If so, I suggest you learn how to NOT use it -- once you get into the kind of tricks we are discussing here, it becomes more of a hindrance than a help -- I don't use it at all, ever.
#Gia Dang's answer is the simplest, but I don't subclass the UIView, only the UIViewController, so my call is slightly different. Also, since I don't know the overhead for actually calling resignFirstResponder, I prefer to check first. It's more code, but since all of this is done on the main thread (which can slow down the UI), I'd rather check first.
#implementation MyController : UIViewController {
#private
UISearchController *_uiSearchController;
}
- (void)viewDidLoad {
// add tap on view to resign the responder if we're in the middle of typing in the search
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeKeyboardIfNeeded)];
[self.view addGestureRecognizer:tapGestureRecognizer];
}
- (void)closeKeyboardIfNeeded {
if (![_uiSearchController.searchBar isFirstResponder]) {
return;
}
[_uiSearchController.searchBar resignFirstResponder];
}
#end
As for the other answers, be careful about constantly recreating objects. There is always a performance hit, whether it's the creation itself or the garbage collection through ARC, and these things will slow down your main thread. Depending on what you're doing also on the main thread, it may have a significant performance impact.
I am trying to recreate something similar to the popup keyboard used in safari.
I am able to visually reproduce it by placeing a toolbar over my view and the appropriate buttons, however i cant figure out any way to dismiss the keyboard once the user has touched the done button.
There is a couple of things you need to remember. The number #1 part developers forget to set is the delegate of the textField.
If you are using the Interface Builder, you must remember that you need to set the delegate of the textField to the file Owner.
If you are not using Interface Builder then make sure you set the delegate of the textfield to self. I also include the returnType. For Example if the textField was called gameField:
gameField.delegate = self;
gameField.returnKeyType = UIReturnKeyDone;
You must also implement the UITextFieldDelegate for your ViewController.
#interface YourViewController : UIViewController <UITextFieldDelegate>
Finally you need to use the textFieldShouldReturn method and call [textField resignFirstResponder]
-(BOOL) textFieldShouldReturn:(UITextField*) textField {
[textField resignFirstResponder];
return YES;
}
All your textFields will use this same method so you only need to have this setup once. As long as the delegate is set for the textField, the UITextFieldDelegate is implemented for the interface, you add the textFieldShouldReturn method and call the
resignFirstResponder your set.
Have you tried:
[viewReceivingKeys resignFirstResponder];
where viewReceivingKeys is the UIView that is receiving the text input?
If your building your own views in Interface Builder, set your view controller to be delegate for the text field and implement textFieldShouldReturn: from UITextFieldDelegate in your views controller.
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField
{
NSLog(#"%# textFieldShouldReturn", [self class]);
[theTextField resignFirstResponder];
// do stuff with the text
NSLog(#"text = %#", [theTextField text]);
return YES;
}
UITextFieldDelegate textFieldShouldReturn: in the iphone cocoa docs
If you're talking about dismissing the keyboard from a UITextField rather than a UITextView. Your question isn't that clear? If you are then ensure your class is marked as a UITextFieldDelegate in the interface file,
#interface MyController: UIViewController <UITextFieldDelegate> {
UITextField *activeTextField;
// ...remainder of code not show ...
}
and then you should implement the two delegate methods as below,
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
activeTextField = textField;!
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
activeTextField = nil;
[textField resignFirstResponder];
return YES;
}
However if you're using a UITextView then things are a bit more complicated. The UITextViewDelegate protocol lacks the equivalent to the textFieldShouldReturn: method, presumably since we shouldn’t expect the Return key to be a signal that the user wishes to stop editing the text in a multi-line text entry dialog (after all, the user may want to insert line breaks by pressing Return).
However, there are several ways around the inability of the UITextView to resign as first responder using the keyboard. The usual method is to place a Done button in the navigation bar when the UITextView presents the pop-up keyboard. When tapped, this button asks the text view to resign as first responder, which will then dismiss the keyboard.
However, depending on how you’ve planned out your interface, you might want the UITextView to resign when the user taps outside the UITextView itself. To do this, you’d subclass UIView to accept touches, and then instruct the text view to resign when the user taps outside the view itself.
Create a new class,
#import <UIKit/UIKit.h>
#interface CustomView : UIView {
IBOutlet UITextView *textView;
}
#end
Then, in the implementation, implement the touchesEnded:withEvent: method and ask the UITextView to resign as first responder.
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}
- (void) awakeFromNib {
self.multipleTouchEnabled = YES;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touches began count %d, %#", [touches count], touches);
[textView resignFirstResponder];
[self.nextResponder touchesEnded:touches withEvent:event];
}
#end
Once you’ve added the class, you need to save all your changes, then go into Interface Builder and click on your view. Open the Identity inspector in the Utility pabel and change the type of the view in your nib file to be your CustomView rather than the default UIView class. Then in the Connections Inspector, drag the textView outlet to the UITextView. After doing so, and once you rebuild your application, touches outside the active UI elements will now dismiss the keyboard. Note however that if the UIView you are subclassing is “behind” other UI elements, these elements will intercept the touches before they reach the UIView layer. So while this solution is elegant, it can be used in only some situations. In many cases, you’ll have to resort to the brute force method of adding a Done button to the navigation bar to dismiss the keyboard.
use a navigation controller and pop the view when done?
for example, I use code like this to slide an about box in:
[[self navigationController] presentModalViewController:modalViewController animated:YES];
and then when the button in that about box is clicked, I use this to get rid of it:
[self.navigationController dismissModalViewControllerAnimated:YES];
In my case the about box occupies the whole screen, but I don't think it would have to for this to work.
edit: I think I may have misunderstood your question. Something along the lines of my code would be if you are faking the whole keyboard view yourself. I think that resign first responder is the right way to do it if it is the normal keyboard with your toolbar added on top.