I've got a view containing subviews of various types on it. At one point, I would like to disable all interactions with the view and subviews, and register instead taps from a gesture recognizer I place on the whole view:
tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:revealController action:#selectorrdoSomething:)];
tapGestureRecognizer.cancelsTouchesInView = YES;
While it sort of works, the view below still interacts to all the touches. I then tried adding:
tapGestureRecognizer.delaysTouchesBegan = YES;
tapGestureRecognizer.delaysTouchesEnded = YES;
It now works EXCEPT - when I tap over a UITextField, this receives the touch INSTEAD of the gesture recognizer. Why is this, how can I stop it? Any help is much appreciated :)
Sounds like what you want to do is set the view you don't want receiving the touches .userInteractionEnabled property to FALSE. Or I am misunderstanding something.
yourView.userInteractionEnabled = FALSE;
Related
I'd like to detect swipe on the entire screen, however, the screen contains UIButtons, and if the user taps one of these buttons, I want the Touch Up Inside event to be triggered.
I've create a UIView on the top of my screen, and added a UIPanGestureRecognizer on it to detect the swipe, but now I need to pass the gesture through that view when I detect that it's a tap rather than a swipe.
I know how to differentiate the gestures, but I've no idea on how to pass it to the view below.
Can anyone help on that? Thanks!
Thanks for your answer. The link helped me to solve part of my problem.
I've set the buttons as subviews of my gestureRecognizer view and I can now start a swipe from one of the buttons (and continue to use the buttons as well). I managed to prevent the buttons to go to the "down" state by using the following code :
UIPanGestureRecognizer *swipe = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(swipeDetected:)];
swipe.maximumNumberOfTouches = 1;
swipe.delaysTouchesBegan =YES;
swipe.cancelsTouchesInView = YES;
[self.gestureRecognitionView addGestureRecognizer:swipe];
there is a BOOL property of UIGestureRecognizer cancelsTouchesInView. default is yes. set it to NO , and the touches will pass thru to the UIView
also have a look at the solution for this question
If you want to prevent the recognizer from receiving the touch at all, UIGestureRecognizerDelegate has a method gestureRecognizer:shouldReceiveTouch: you can use:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// don't override any other recognizers
if (gestureRecognizer != panRecognizer) {
return YES;
}
CGPoint touchedPoint = [touch locationInView:self.someButton];
return CGRectContainsPoint(self.someButton.bounds, touchedPoint) == NO;
}
I'm trying to add a UIPanGestureController to my UITableView so I can detect whether the user is manually panning or they just gave an initial kick and watch the view scroll by itself. The reason is that I want to snap to a cell as soon as the scrolling slows down (imagine a wheel of fortune). Of course I don't want to snap when the user is panning manually.
However, I can either use the gesture controller (and set my "is scrolling manually" variables accordingly") OR scroll the view.
Using TouchBegin events instead of the gesture recognizer introduces new problems, so that's not really an option.
First I changed the table view to include the UIGestureRecognizerDelegate.
After initializing the view, I then do...
panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panTableView:)];
[panGestureRecognizer setDelegate:self];
[self.view addGestureRecognizer:panGestureRecognizer];
I implement the Begin function, to set a BOOL:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
isPanning = YES;
return NO;
}
Always returning NO should make sure that the gesture recognizer is never active, since I want (and need) to use the table view's own scroll methods.
Problem: my action "panTableView" is never called.
If I don't set the delegate, the action is called, but I can't scroll, since the gesture recognizer catches all my touches.
I already looked into the targets. After setting the delegate, the gesture recognizer's view and delegate pointers are the same as self.view, the action still targets self with the right selector.
try
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
isPanning = YES;
return YES;
}
and implement this
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES
}
I have a scrollview in my Main view and I have three subviews on my scrollview. And I have UIButtons in all my subviews.
Now, I want to drag those buttons from one subview to another subview (while dragging the buttons, the scrollview should not scroll).
How do I do this?
I'm not completely sure if this snippet works for this particular case (an UIControl inside a UIScrollView), but my understanding of UIResponder chain suggests me that it should :)
- (void)viewDidLoad { // or wherever you initialize your things
...
// Add swipe event to UIButton so it will capture swipe intents
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] init];
[panGR addTarget:self action:#selector(panEvent:)];
[button addGestureRecognizer:panGR];
[panGR release];
}
- (void)panEvent:(id)sender {
button.center = [sender locationInView:self.view];
}
If this works (can't test it right now, but it did work for me in a similar situation), then you should add more code to handle the drag & drop related events (maybe disable Clip Subviews option in the UIScrollView, add the button to the new superview if the location intersects with the CGRect of the destination, return the button to the original location if it doesn't, etc).
So, what's happening in those lines? When you begin touching the UIButton, the order doesn't get to the UIScrollView because the event could follow as a touch event (handled by the UIButton), or as a pan event (handled by the UIScrollView). When you move your finger, the event is dismissed by the UIButton's responder because there's no Gesture Recognizer that knows how to proceed if the finger is moved.
But when you add a Gesture Recognizer to the UIButton who actually knows what to do when the finger is moved, everything is different: the UIButton will not dismiss the event, and the UIScrollView will never realize that there was a touch moving over it.
I hope my explanation is accurate and comprensible enough. Let me know if a) it doesn't work or b) there's something unclear.
Good luck :)
Try
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (allowAppDrag && [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return NO;
}
return YES;
}
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.
}
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 .