I have a subclassed UIView, let's call it TileView.m/h.
In TileView.m, I have the following code: ([setup] is definitely being called, I checked with breakpoints).
- (void)setup {
self.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(testPressed:)];
[self addGestureRecognizer:tap];
}
- (void)testPressed:(UITapGestureRecognizer *)sender {
NSLog(#"tap pressed");
}
For some reason, the testPressed method is not called when I tap the view!
Strangely, if I copy the TileView class into a blank xCode project and set it up there, everything works absolutely fine. This is peculiar since gestures are handled first by subviews and then by superviews - so the tile class shouldn't be affected by its superviews. Also the gesture is fully contained within the TileView class.
For reference, the tile view is nested fairly deeply in a:
Controller -> controller's view -> scroll view -> container view -> tile view pattern.
I have tried without success:
Setting userInteractionEnabled = YES on TileView and all its subviews.
Removing all gesture recognizers from other parts of code to avoid interference.
Setting tap.delegate = self and then implementing gestureRecognizer:shouldRecognizeSimultaneously... to always return YES;
No luck :/
Any ideas for what else could be going wrong? Or what tests can I run to understand the problem in more detail?
EDIT: More details
Recall that the tile view is nested in: Controller -> controller's view -> scroll view -> container view -> tile view
If I add a gesture recognizer in the controller file with:
[self.view addGestureRecognizer:tap]
and then use [self.view hitTest:[sender locationInView:self.view] withEvent:nil]; in the event handler, I can see that the returned view is a UIScrollView and not a TileView as would be expected.
Additionally, adding the gesture recognizer to self.scrollView instead works fine, but stops working if I add it to self.containerView (which is the only subview of scrollView). containerView has userInteractionEnabled = YES.
If the view contains subviews that are UIImageViews, remember that you have to explicitly set the userInteractionEnabled to YES (the default is NO).
If not, the image views will prevent the tap from reaching the superview.
Also, remember to set the number of touches.
You might also want to check that you're not trying to reuse the UITapGestureRecognizer object for different views.
It seems like it's possible because you're just appending the gesture to the view, but in ultimately the view needs to be assigned to the view property on the gesture object so adding the gesture object to a second view will just overwrite the first.
The reason why its not firing is because you are not keeping a reference to your UITapGestureRecognizer. As soon as setup function returns your UITapGestureRecognizer gets deallocated.
If you want it to work save it to a property:
#property (strong, nonatomic) UITapGestureRecognizer *tapRecognizer;
- (void)setup {
self.tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(testPressed:)];
[self addGestureRecognizer:self.tapRecognizer];
}
- (void)testPressed:(UITapGestureRecognizer *)sender {
NSLog(#"tap pressed");
}
Related
i am making a chat application, and in the chat window there are uilabels in uitableviewcells. initially the keyboard will be present but when the user touches on any place on uitableview, i will make the chat window as fullscreen (dissappearing keyboard).
i cant find a way/trick to accomplish this.
i have tried the following method: by using tableview:didselectrowatindexpath, i am able to do it but, user needs to press on an existent uitableviewcell. but i want to understand the press even when uitableview is empty..
note: my chat tableview is interactive e.x. some rows will include image button which need to be pressable, so i cant just put an invisible button onto uitableview.
thank you for your thoughts
Aytunc Isseven
What you want to do is add a gesture recognizer to the UITableView that responds to the appropriate gestures. I would recommend against using UITapGestureRecognizer as the UITableView is already using taps for selecting the cells, so you might want to try the UILongPressGestureRecognizer. I put together a small sample of how you can do this as follows:
In my viewDidLoad I did the following:
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPressFrom:)];
[self.tableView addGestureRecognizer:gesture];
and the handleLongPressFrom: is as follows:
- (void)handleLongPressFrom:(UILongPressGestureRecognizer *)recognizer {
NSLog(#"handleLongPressFrom: %#", recognizer);
// Add real code here
}
The full list of gestures can be found here.
Oh, if you did want to still use tap, check out this stack overflow question. I don't know if the method presented works fully, but it'd be a good place to start.
Using UITapGestureRecognizer with a UITableView:
Okay, since the tap gesture seems to be the correct one for your use case you can try and do the following. Step 1 is to set up the gesture recognizer as I listed above using the tap gesture instead of the long press gesture.
The code in viewDidLoad is very similar with an important addition ...
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom:)];
// we need to set the gesture delegate so we can allow the tap to pass through to the
// UITableViewCell if necessary.
gesture.delegate = self;
[self.tableView addGestureRecognizer:gesture];
The handleTapFrom: function is pretty much the same with just the different gesture recognizer as the parameter.
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
NSLog(#"handleTapFrom: %#", recognizer);
// Add real code here
}
The major changes to this approach is that we need to implement the UIGestureRecognizerDelegate protocol. Since our goal is to allow the tap gesture to pass through the UITableView to it's subviews (i.e. the UITableViewCell and it's components) we need to implement the gestureRecognizer:shouldRecieveTouch: function. The following implementation should cover what you are attempting.
#pragma mark UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// If the view that is touched is not the view associated with this view's table view, but
// is one of the sub-views, we should not recognize the touch.
if (touch.view != self.tableView && [touch.view isDescendantOfView:self.tableView]) {
return NO;
}
return YES;
}
The isDescendantOfView: function returns YES if the view it is testing against is the same as the view doing the testing, so we need to accommodate that case separately. You can generify this function by using gestureRecognizer.view instead of self.tableView, but I didn't think it was necessary in this case.
The trick is to make your viewController put a tap recognizer on the view but make it always opt out by returning NO from the delegate method "gestureRecognizerShouldBegin". That way gestureRecognizerShouldBegin gets called for every touch on the view, but you don't interfere with the normal event handling of the table.
- (void)viewDidLoad {
[super viewDidLoad];
[self detectTouchesOnView:self.tableView];
}
- (void)detectTouchesOnView:(UIView*)theView {
UITapGestureRecognizer* tapR = [[UITapGestureRecognizer alloc]initWithTarget:nil action:nil];
tapR.delegate = self;
[theView addGestureRecognizer:tapR];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// React to the UITableView being touched E.G. by hiding the keyboard as below.
[self.view endEditing:YES];
return NO;
}
I have a ViewController that responds to some touchEvents (touchesBegan, touchesMoved, etc...).
I've found that when I show this controller using presentModalViewController: it works just fine, but I'm trying to add it's View as a subview of another ParentViewController like so:
- (void)viewDidLoad
{
[super viewDidLoad];
//Add SubController
controller = [[SubViewController alloc] initWithNibName:#"SubViewController" bundle:nil];
controller.view.frame = CGRectMake(0, 30, 300, 130);
[view addSubview:controller.view];
[controller release];
}
When I do this, it gets added the parent view but it no longer responds to touch events. Is there a way to fix this?
Also, is there a better way to go about this? I know I probably could have used a View subclass for the child view, but it's supposed to use a Nib and I wasn't sure how to handle that without using a ViewController.
You're correct you should use a UIView subclass.
The easiest way to load it from a nib is to include the subview in your nib.
Just drop a UIView into the view connected to the original view controller.
Then with the view inside selected go to the identity inspector. It's the one that looks like a little ID card.
The very first field is called Custom Class.
Type the name of your UIView subclass here.
If you need a reference to this just create an IBOutlet in your original view controller and hook it up. That way you can set hidden = YES until you need it.
In your UIView subclass you might want to override
- (void)awakeFromNib
This will get called when the nib first unpacks.
for setting up any gesture recognizers, etc.
To load a nib directly into a view :
// Get the views created inside this xib
NSArray *views = [NSBundle mainBundle] loadNibNamed:#"myViewNib" owner:nil];
// There's probably only one in there, lets get it
UIView *myView = [views objectAtIndex:0];
// Do stuff . . .
[[self view] addSubview:myView];
You could try to call becomeFirstResponder in your subview and see whether it receives touchesBegan... It is probably so, but it will also possibly make the superview not receive touchesBegan if you require it...
I've a UITextView (from the InterfaceBuilder) in my View. This contains a UIScrollView.
Now I have to detect touch Events inside the UITextView beacause I must close my own submenus.
The Apple UITextView is in a UIScrollView, and to detect a touch in a UIScrollView I've to ovveride the UITouch Funktion of UITextView's UIScrollView.
Any suggestions how I can do this?
UITextView is a subclass of UIScrollView. Is this what you mean when you say "contains a UIScrollView?"
There are a couple of approaches you could take here. If the touch you are concerned with is the first touch in the text view, and is therefore beginning editing, you can become its delegate and implement this method:
- (void)textViewDidBeginEditing:(UITextView *)textView
If you need to be aware of any tap that occurs inside the text view, not just initial editing taps, you can use a UITapGestureRecognizer to listen for taps. Something like this:
// in the method where you configure your view
UITapGestureRecognizer *tap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textViewTapped:)] autorelease];
// assuming textView is pointing to your UITextView
[textView addGestureRecognizer:tap];
And then implement the action method elsewhere in your class:
- (void)textViewTapped:(id)sender {
// dismiss your menu or whatever
}
Note that I haven't actually tested this scenario, but it should work. Gesture recognizers are awesome.
I want to call an action in two classes (a superview and a full screen subview) when the user single taps the screen. But, when I add a UITapGestureRecognizer to the subview, the one added to the superview is overridden. Is it possible to add a UITapGestureRecognizer to a subview without overriding the UITapGestureRecognizer added to the superview?
If so, how can I do this?
Thanks!
Edit:
From my main viewController "MyToolBerController", I'm adding the subview from another viewController as follows:
PhotoViewController *photoViewController = [[PhotoViewController alloc] initWithNibName:#"PhotoViewController" bundle:nil];
myPhotoView = photoViewController.view;
[self.view addSubview:myPhotoView];
I add the GestureRecognizer in the MyToolBerController like this:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTapFrom:)];
[singleTap setNumberOfTapsRequired:1];
singleTap.delegate = self;
[myPhotoView addGestureRecognizer:singleTap];
[singleTap release];
This all works fine, but I need to call a method in the PhotoViewController class when the view is tapped as well as in the MyToolBerController class.
When I add another UITapGestureRecognizer in the photoViewController, it overrides the UITapGestureRecognizer added in the superView.
Gesture recognizers can dispatch multiple actions when the gesture occurs. You can add the subview as another target of the gesture recognizer and only use a single UITapGestureRecognizer instance:
[tapRecognizer addTarget:theSubview action:#selector(whatever:)];
In your gesture recognizer selector method, pass the information along to the subview. There's no need to have multiple gesture recognizers for the same gesture. Something like:
- (IBAction)handleSingleDoubleTap:(UIGestureRecognizer *)sender
{
CGPoint tapPoint = [sender locationInView:sender.view.superview];
UIView *subview = [parentView viewWithTag:100];
[subview doSomethingWithPoint:tapPoint];
}
This of course means that your subview that needs to be notified should be given the tag 100 either in Interface Builder or in code when the view controller gets loaded.
Update based on Jonah's code:
So instead of retaining the view, retain the view controller:
PhotoViewController *photoViewController = [[PhotoViewController alloc] initWithNibName:#"PhotoViewController" bundle:nil];
self.myPhotoViewController = photoViewController;
Which means you need to declare it this way in the MyToolbarController header:
#property (nonatomic, retain) PhotoViewController *myPhotoViewController;
Then, when your gesture selector gets called, pass the message along to the view controller you retained. Something like:
- (IBAction)handleSingleTapFrom:(UIGestureRecognizer *)sender
{
CGPoint tapPoint = [sender locationInView:sender.view.superview];
[myPhotoViewController doSomethingWithPoint:tapPoint];
}
Of course the -doSomethingWithPoint: method is only for example. You can name and create any method you want that takes any parameter you want to pass in your PhotoViewController.
Let me know if you need further clarification.
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.