CODE
I have some code that adds a UILongPressGestureRecognizer gesture recognizer called _recognizer to a subclass of a UITableViewCell called cell:
...
UILongPressGestureRecognizer *_recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(cellLongPressRecognized:)];
_recognizer.allowableMovement = 20;
_recognizer.minimumPressDuration = 1.0f;
[[cell contentView] addGestureRecognizer:_recognizer];
[_recognizer release];
...
The -cellLongPressRecognized: selector simply logs when the gesture ends:
- (void) cellLongPressRecognized:(id)_sender {
if (((UILongPressGestureRecognizer *)_sender).state == UIGestureRecognizerStateEnded)
ALog(#"[MyViewController] -cellLongPressRecognized: gesture ended...");
}
My console shows one log message when I tap, hold and release a cell:
[MyViewController] -cellLongPressRecognized: gesture ended...
So far, so good.
ISSUE
The issue is that the table cell's background stays selected only as long as 1.0 second, the _recognizer.minimumPressDuration property.
If I hold my finger on the device any longer than 1.0 second, the cell's background flips back from the UITableViewCellSelectionStyleBlue selection style to its usual, opaque, non-selected background.
To make sure only gesture-specific code is involved with this issue, I have disabled -tableView:didSelectRowAtIndexPath: while testing.
QUESTION
How do I keep the background selected indefinitely, flipped back only when the "long press" gesture ends?
I changed my test condition from UIGestureRecognizerStateEnded to UIGestureRecognizerStateBegan and the gesture is timed with the cell selection state change:
- (void) cellLongPressRecognized:(id)_sender {
if (((UILongPressGestureRecognizer *)_sender).state == UIGestureRecognizerStateBegan)
ALog(#"[MyViewController] -cellLongPressRecognized: gesture began...");
}
Seems counterintuitive naming the event this way, but that seems to work.
Related
I added a UIPanGestureRecognizerto my UIButton. And now my when I drag a short distance with my finger after I pressed the button, the button stops being highlighted. Normally, a UIButton will stay highlighted unless you drag pretty far outside the UIButtons frame. However, with a pan gesture recognizer, now even if I drag a little bit, the button stops being highlighted.
Nothing in my code sets the buttons highlighted property to no. I even tried erasing all the code in my panning gesture recognizers action selector thing(the method that gets called whenever i pan on my button).
I also tried setting the button's highlighted property to NO in the panning gesture recognizer's action selector thing.
This kind of worked its just the highlightedness flashes. When you pan the highlighted goes away then comes back really fast, like a flash. So, this doesnt work too. Any ideas?
Gestures, by default, will cancel the touches in the views that they are linked to. So, when the touches in your button get cancelled it becomes unhighlighted. To prevent this behavior, set the cancelsTouchesInView property of your gesture to NO.
This will work
-(void) panDetected:(UIGestureRecognizer *) gesture
{
if(gesture.state == UIGestureRecognizerStateBegan)
{
yourButton.backgroundImageView.image = [UIImage imageNamed:#"your_button_highlited_image.png"];
}
else if(gesture.state == UIGestureRecognizerStateChanged)
{
}
else if(gesture.state == UIGestureRecognizerStateEnded)
{
yourButton.backgroundImageView.image = [UIImage imageNamed:#"your_button_normal_image.png"];
}
}
I have experienced the same problem. borrrden is right; to fix it, set the viewController the delegate for the gestureRecognizer, and cancel the touch if the touch is on a IUButton. First add the protocol `UIGestureRecognizerDelegate on the .h file. Then, after creating the gesture recognizer, set the view controller as its delegate:
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(aMethod)];
gestureRecognizer.delegate = self;
[self.view addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
Finally override shouldReceiveTouchand cancel unwanted touches:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
if ([touch.view isKindOfClass:[UIButton class]]) { //Do not override UIButton touches
return NO;
}
return YES;
}
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'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;
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;
}
I have a button on a subview view (for talk sake the subview is a red square) that when the user holds down on the button the red square animates translucent.
I have the button connected to this method:
-(IBAction)peekToggle:(id)sendr{
NSLog(#"TOGGLE");
if(self.view.alpha ==1)self.view.alpha = 0.1;
else self.view.alpha = 1;
}
Via the behaviours: touch up inside, touch up outside and touch down. so when i hold the button down the red box goes transluscent and when i release my finger it returns to opaque.
This initially works fine, however if i hold the button down for more than 1 second, the button does not register the touch up (release of the finger).
NB:I do have a longPressGestureRecogniser on the parent view (parent of subview not parent of Button) but its not being fired (expected).
Im pretty sure my long press on the button being registered as a touch cancel and then invalidating the touch up event.
How can I prevent/work around this?
Can I Stop the touch Cancel Firing? (this event seems to fire even if i havant registered the control state) or in the touch Cancel event, tell the button to keep/start registering events?
SOLUTION:
Removed the IBActions completely and added UILongPressGestureRecognizer to the button with a very short min duration.
UILongPressGestureRecognizer * recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
recognizer.minimumPressDuration = 0.1;
[self.peekButton.view addGestureRecognizer:recognizer];
[recognizer release];
Then in the selector for the gr, read the gr's state:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer{
//1 = start
if(gestureRecognizer.state==1 || gestureRecognizer.state==3)[self peekToggle];
//3=end
}
If you think that's your problem, you can try overriding - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
and see if you get any activity there.
You can use the UIGestureRecognizerDelegate interface to fine-tune when your gesture recognizer gets fired.