How to handle touch event on UILabel as subview of UITableViewCell? - iphone

My app has a custom UITableView. In the cellForRowAtIndexPath delegate method of its UIViewController I am instantiating custom UITableViewCell objects that contain multiple custom UILabels (actually a sub-class of OHAttributedLabel) as subviews of the content view.
I have tried setting userInteractionEnabled = YES on the label, then adding touch events in the view controller, but that isn't working.
Ideas?
Thanks

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if (CGRectContainsPoint([self.site frame], [touch locationInView:self.view])){
//do whatever you want
}
}
Or
UILabel *label = =[UILabel alloc]init];
label.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture =
[[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(labelTap)] autorelease];
[label addGestureRecognizer:tapGesture];

A UILabel isn't a UIControl so you won't get events on UIControlEventTouchUpInside or similar. Why not use a button instead? You can make it look exactly like a label.
Regardless you will probably need to set addTarget:action:forControlEvents: and tag on the UIButton in your cellForRowAtIndexPath: method. In that method, detect which cell's button was tapped by examining the tag value.
If you must use UILabel then you need to subclass it and intercept the touchesBegan/touchesEnded methods (inherited from UIResponder) to detect UIControlEventTouchUpInside yourself.

Problem in OHAttributedLabel. This label also handles tap on links. So for handle tap on any point of label (not just link) you must
self.textLabel.onlyCatchTouchesOnLinks = NO;
Where self.textLabel is your OHAttributedLabel.
And don't forget of userInteractionEnabled.

I don't know if it is the same problem but... I added a label and could not get it to recognize a touch, I eventually realised that it was because I was adding it as a subview, but its frame was outside its parent's frame, hence the touch heirarchy broke

I just had the problem with using static table cells for a settings table where I wanted the whole cell to trigger the first responder for the cell's textfield.
I ended up adding a transparent (custom, blank title) button behind the label (touch disabled) and textfield after not getting any touches using gesture recognizers. I think it should work in a more elegant way, but it solved the task for now and that limited purpose. (and you can just drag connect from the button's default action)
Bit ugly. Then again, it just describes the area behind the text field reacting to touch. Which was the intention after all. So maybe its just not that fancy.
Will keep it until I find the reason for the recognizers not firing.

you can use TTTAttributedLabel to instead it. it's very easy.
when you initial the UITableViewCell,you can delegate:TTTAttributedLabelDelegate
like :
#interface MyTableViewCell : UITableViewCell<TTTAttributedLabelDelegate>{
UILabel *nameLabel;
TTTAttributedLabel *valueLabel;
}
when you initial ,you could add link to label :
[valueLabel addLinkToPhoneNumber:valueStr withRange:NSMakeRange(0, valueStr.length)];
so,you could do anything you want:
- (void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithPhoneNumber:(NSString *)phoneNumber{
//do anything you want.
}

Related

How to perform some Action on imageview clicked?

I'm working on a photo Collage application. I want to perform an action on image view so any one can help me to solve my problem. when i pick image from photo library and shown on image view after when double click on image view i want to push or present a new view to crop this image .....so anyone tell me what i do to solve my problem...
Use a UIButton of type custom and put an image inside it. And you are done.
Add the Gesture for ImageView
UITapGestureRecognizer *sg=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(HandleEvent)];
sg.numberOfTapsRequired=2;
[BottomBar addGestureRecognizer:sg];
[sg release];
and do your task here:
-(void)HandleEvent
{
// your task
}
Thanks
You should use a UIButton instead of an UIImageView. You can set its style to custom and then asign it an image. UIButton provides all the methods for double tap already built in.
If you are using UIImageView, make sure you use setUserInteractionEnabled:YES. Then you need to check your touches in touchesBegan, ended, (canceled).
You are set on using UIImageView, you will have to subclass UIImageView and override the touch methods you want to intercept. For example:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Call super to make sure you don't lose any functionality
[super touchesBegan:touches withEvent:event];
// Perform the actions you want to perform with the tap.
}
Using UIButtons as replacements for your UIImageViews is a great idea you can just drag a connection from each button to an outlet in your view controller and handle the action. You can set the background image on the buttons as you are doing currently with the UIImageViews. Set your button type to custom and you'll be good to g
If you are using UIImageView class ,first set setUserInteractionEnabled:YES . Then set UITapGestureRecognizer event to image view.
try this
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint locationPoint = [[touches anyObject] locationInView:self.view];
CGPoint viewPoint = [imageView convertPoint:locationPoint fromView:self.view];
if ([imageView pointInside:viewPoint withEvent:event]) {
//do something
}
}
Make a button, and clear its color. On the back of button, put your image view, so that your imageview seems to be visible and the button doesn't. Now attach event to the button.

hitTest:withEvent: Not Working

I'm making an app where I have a background view and that has six UIImageView's as subviews. I have a UITapGestureRecognizer to see when one of the UIImageViews is tapped on and thie handleTap method below is what the gesture recognizer calls. However, when I run this, the hitTest:withEvent: always returns the background view even when I tap on one of the imageViews. Does it have something to do with the event when I call hitTest?
Thanks
- (void) handleTap: (UITapGestureRecognizer *) sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView: sender.view];
UIView * viewHit = [sender.view hitTest:location withEvent:NULL];
NSLog(#"%#", [viewHit class]);
if (viewHit == sender.view) {}
else if ([viewHit isKindOfClass:[UIImageView class]])
{
[self imageViewTapped: viewHit];
NSLog(#"ImageViewTapped!");
}
}
}
UIImageView are, by default, configured to not register user interaction.
From the UIImageView documentation:
New image view objects are configured to disregard user events by
default. If you want to handle events in a custom subclass of
UIImageView, you must explicitly change the value of the
userInteractionEnabled property to YES after initializing the object.
So, right after you initialize your views you should have:
view.userInteractionEnabled = YES;
This will turn the interaction back on and you should be able to register touch events.
There's a rewrite on your approach (single GR on the containing view) that works, but it'll make our brain hurt getting the coordinate systems right, which is definitely the problem in the posted code.
The better answer is to attach N gesture recognizers to each of the UIImageViews. They can all have the same target and use the same handleTap: method. The handleTap: can get the view without searching any geometry like this:
UIImageView *viewHit = (UIImageView *)sender.view;

How to change the style of an UIView when it's tapped?

I'm making different UIView's tappable (they're not inheriting from UIControl) using the following code:
UITapGestureRecognizer* gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(userTappedOnLink:)];
[labelView setUserInteractionEnabled:YES];
[labelView addGestureRecognizer:gesture];
but I'd also like to change the style when they're highlighted. How do I do that?
Attach UILongPressGestureRecognizer instead of UITapGestureRecognizer to parent view and set it's properties to your liking. The way to track and respond to selection is to implement userTappedOnLink method in appropriate way. This method will be called lots of times in short amount of time when gesture recognizer is activated and you know what's happening by tracking recognizer states.
Implement UIView subclass and create methods, like select and deselect, and customize view properties for each. Then it's only matter of finding which UIView subclass to select or deselect and that's easily done with UIGestureRecognizer method returning point in parent view and iterating trough it's subviews while checking if touch point is inside of particular subview frame.
- (IBAction)userTappedOnLink:(UIGestureRecognizer*)sender
{
switch (sender.state)
{
case UIGestureRecognizerStateBegan:
{
CGPoint touchPoint = [sender locationInView:self.parentView];
for (UIView *subView in [self.parentView subViews)
{
if (CGRectContainsPoint(subView.frame, tapPoint))
{
self.activeSubView = self.subview;
break;
}
}
[self.activeSubView select];
case UIGestureRecognizerStateChanged:[self.activeSubView doNothing];; break;
case UIGestureRecognizerStateEnded:[self.activeSubView deSelect]; self.activeSubView = nil; break;
}
}
There are two ways of handling events in iOS. The first one is to use UIView subclassing and override the methods that are inherited by UIView from UIResponder class (i.e. touchesBegan:withEvent, touchesMoved:withEvent, touchesEnded:withEvent, touchesCancelled:withEvent).
The second way is to create new instance of gesture recognizer class and add it to your UIView object (just like you did) and then create a handler.
- (IBAction)userTappedOnLink:(UIGestureRecognizer *)sender {
// Changing view properties.
}
In both cases you can change the UIView properties. You can find some useful information in "Event handling guide" by Apple. There is a lot of reading but you can have a look only at the "related sample code" (Touches and SimpleGestureRecognizers).
The way you change the style properties of the interface elements in your application depends on what those properties are. Sometimes they can be animated sometimes they're not. Usually the code that changes view properties is placed inside the touchesBegin function or gesture recognizer handler. MoveMe sample code shows how to change views properties and animate them. In the Gesture recognizers chapter of the "Event handling guide" frame properties are changed.
I managed to solve this by adding an UIControl as a subview in the UIView. The UIControl is the same size has a transparent background that changes when it's highlighted. Works like a charm!
well, I have not tested it, just a suggestion, please handle touchesbegin for this view,and call [labelView addGestureRecognizer:gesture]; function.
maybe you should use another function, - (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer .

Add TapGestureRecognizer to UITextView

I want to add an *UITapGestureRecognize*r to my UITextView, because I want to close a "Popup" where the TextView is in. So I want, that the method "hide" of the Popup class is called, when the T*extView* is tapped. I tried it like the following, but it isn't working for some reason:
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(show)];
[gr setNumberOfTapsRequired:1];
[viewText addGestureRecognizer:gr];
I also don't want to create a Subclass for it, because I then would need to call the "parent"-method "hide".
Maybe you now a good solution for that problem.
Thank you in advance.
You shouldnt use UITapGestureRecognizer but use the UITextFieldDelegate.
You can read about it here:
http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UITextViewDelegate_Protocol/Reference/UITextViewDelegate.html%23//apple_ref/doc/uid/TP40006897
You basicly need to add the
UITextViewDelegate to your .h file like that -
#interface MyViewController : UIViewController<UITextViewDelegate>
Then assign your controller as the delegate:
viewText.delegate =self;
Now use one of the delegation methods, maybe:
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
// Do what you need to do...
}
Edit
Well I can think on 2 additional approaches:
You can wrap your textView inside a UIView and add the UITapGestureRecognizer to the view.
You can use :
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:textView];
//Checks if the tap was inside the textview bounds
if (CGRectContainsPoint(textView.bounds, location)){
//do something
}
}
Good luck
Did you try to NSLog on show method? or did you even declare and write method "show" ? It should work and that's how I did on my text view.
P.S don't forget to release your gesture instance (gr) after you add on textview :D
I had major problems getting this working also but I had one stupid problem, user interaction was turned off in the visual editor. Hope this helps someone :)

How to disable touch input to all views except the top-most view?

I have a view with multiple subviews. When a user taps a subview, the subview expands in size to cover most of the screen, but some of the other subviews are still visible underneath.
I want my app to ignore touches on the other subviews when one of the subviews is "expanded" like this. Is there a simple way to achieve this? I can write code to handle this, but I was hoping there's a simpler built-in way.
Hope this help...
[[yourSuperView subviews]
makeObjectsPerformSelector:#selector(setUserInteractionEnabled:)
withObject:[NSNumber numberWithBool:FALSE]];
which will disable userInteraction of a view's immediate subviews..Then give userInteraction to the only view you wanted
yourTouchableView.setUserInteraction = TRUE;
EDIT:
It seems in iOS disabling userInteraction on a parent view doesn't disable userInteraction on its childs.. So the code above (I mean the one with makeObjectsPerformSelector:)will only work to disable userInteraction of a parent's immediate subviews..
See user madewulf's answer which recursively get all subviews and disable user interaction of all of them. Or if you need to disable userInteraction of this view in many places in the project, You can categorize UIView to add that feature.. Something like this will do..
#interface UIView (UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value;
#end
#implementation UIView(UserInteractionFeatures)
-(void)setRecursiveUserInteraction:(BOOL)value{
self.userInteractionEnabled = value;
for (UIView *view in [self subviews]) {
[view setRecursiveUserInteraction:value];
}
}
#end
Now you can call
[yourSuperView setRecursiveUserInteraction:NO];
Also user #lxt's suggestion of adding an invisible view on top of all view's is one other way of doing it..
There are a couple of ways of doing this. You could iterate through all your other subviews and set userInteractionEnabled = NO, but this is less than ideal if you have lots of other views (you would, after all, have to subsequently renable them all).
The way I do this is to create an invisible UIView that's the size of the entire screen that 'blocks' all the touches from going to the other views. Sometimes this is literally invisible, other times I may set it to black with an alpha value of 0.3 or so.
When you expand your main subview to fill the screen you can add this 'blocking' UIView behind it (using insertSubview: belowSubview:). When you minimize your expanded subview you can remove the invisible UIView from your hierarchy.
So not quite built-in, but I think the simplest approach. Not sure if that was what you were thinking of already, hopefully it was of some help.
Beware of the code given as solution here by Krishnabhadra:
[[yourSuperView subviews]makeObjectsPerformSelector:#selector(setUserInteractionEnabled:) withObject:[NSNumber numberWithBool:FALSE]];
This will not work in all cases because [yourSuperView subviews] only gives the direct subviews of the superview. To make it work, you will have to iterate recursively on all subviews:
-(void) disableRecursivelyAllSubviews:(UIView *) theView
{
theView.userInteractionEnabled = NO;
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
-(void) disableAllSubviewsOf:(UIView *) theView
{
for(UIView* subview in [theView subviews])
{
[self disableRecursivelyAllSubviews:subview];
}
}
Now a call to disableAllSubviewsOf will do what you wanted to do.
If you have a deep stack of views, the solution by lxt is probably better.
I would do this by putting a custom transparent button with the same frame as the superView. And then on top of that button I would put view that should accept user touches.
Button will swallow all touches and views behind it wouldn't receive any touch events, but view on top of the button will receive touches normally.
Something like this:
- (void)disableTouchesOnView:(UIView *)view {
UIButton *ghostButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)];
[ghostButton setBackgroundColor:[UIColor clearColor]];
ghostButton.tag = 42; // Any random number. Use #define to avoid putting numbers in code.
[view addSubview:ghostButton];
}
And a method for enabling the parentView.
- (void)enableTouchesOnView:(UIView *)view {
[[view viewWithTag:42] removeFromSuperview];
}
So, to disable all views in the parentViev behind yourView, I would do this:
YourView *yourView = [[YourView alloc] initWithCustomInitializer];
// It is important to disable touches on the parent view before adding the top most view.
[self disableTouchesOnView:parentView];
[parentView addSubview:yourView];
Just parentView.UserInteractionEnabled = NO will do the work.
Parent view will disable user interaction on all the view's subviews. But enable it does not enable all subviews(by default UIImageView is not interactable). So an easy way is find the parent view and use the code above, and there is no need to iterate all subviews to perform a selector.
Add a TapGestureRecognizer to your "background view" (the translucent one which "grays out" your normal interface) and set it to "Cancels Touches In View", without adding an action.
let captureTaps = UITapGestureRecognizer()
captureTaps.cancelsTouchesInView = true
dimmedOverlay?.addGestureRecognizer(captureTaps)
I will give my 2 cents to this problem.
Iteratively run userInteractionEnabled = false it's one way.
Another way will be add a UIView like following.
EZEventEater.h
#import <UIKit/UIKit.h>
#interface EZEventEater : UIView
#end
EZEventEater.m
#import "EZEventEater.h"
#implementation EZEventEater
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = false;
}
return self;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//EZDEBUG(#"eater touched");
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
In your code you add the EZEventEater view to cover all the views that your may block the touch event.
Whenever you want to block the touch event to those views, simply call
eater.userInteractionEnabled = YES;
Hope this helpful.
In Swift 5, I achieved this behaviour by placing a view right on top(the highlighted one) and setting:
myView.isUserInteractionEnabled = true
This does not let the touches go through it, thus ignoring the taps.
For my app, I think it will be sufficient to disable navigation to other tabs of the app (for a limited duration, while I'm doing some processing):
self.tabBarController.view.userInteractionEnabled = NO;
Also, I disabled the current view controller--
self.view.userInteractionEnabled = NO;
(And, by the way, the recursive solutions proposed here had odd effects in my app. The disable seems to work fine, but the re-enable has odd effects-- some of the UI was not renabled).
Simple solution. Add a dummy gesture that does nothing. Make it reusable by adding it to an extension like this:
extension UIView {
func addNullGesture() {
let gesture = UITapGestureRecognizer(target: self,
action: #selector(nullGesture))
addGestureRecognizer(gesture)
}
#objc private func nullGesture() {}
}
setUserInteractionEnabled = NO on the view you want to disable
I had the same problem, but the above solutions did not help.
I then noticed that calling
super.touchesBegan(...) was the problem.
After removing this the event was only handled by the top-most view.
I hope this is of help to anybody.