How do we detect a tap & hold on a UITableViewCell?
In iOS 3.2 or later you can use UILongPressGestureRecognizer
Here's the code lifted straight from my app. You should add these methods (and a boolean _cancelTouches member) to a class you derive from UITableViewCell.
-(void) tapNHoldFired {
self->_cancelTouches = YES;
// DO WHATEVER YOU LIKE HERE!!!
}
-(void) cancelTapNHold {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(tapNHoldFired) object:nil];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self->_cancelTouches = NO;
[super touchesBegan:touches withEvent:event];
[self performSelector:#selector(tapNHoldFired) withObject:nil afterDelay:.7];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self cancelTapNHold];
if (self->_cancelTouches)
return;
[super touchesEnded:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[self cancelTapNHold];
[super touchesMoved:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self cancelTapNHold];
[super touchesCancelled:touches withEvent:event];
}
//Add gesture to a method where the view is being created. In this example long tap is added to tile (a subclass of UIView):
// Add long tap for the main tiles
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longTap:)];
[tile addGestureRecognizer:longPressGesture];
[longPressGesture release];
-(void) longTap:(UILongPressGestureRecognizer *)gestureRecognizer{
NSLog(#"gestureRecognizer= %#",gestureRecognizer);
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
NSLog(#"longTap began");
}
}
You should probably handle the UIControlTouchDown event and depending on what you mean by "hold", fire a NSTimer that will count an interval since you initiated the touch and invalidate upon firing or releasing the touch (UIControlTouchUpInside and UIControlTouchUpOutside events). When the timer fires, you have your "tap & hold" detected.
Related
So, I have a custom gesture recognizer added to a skmap, but the behavior is not consistent but rather weird. At first, both map and my gesture recognizer respond to gesture events.
But after panning or pinch to rotate & zoom, only gesture recognizer responds to gestures. While map does not respond to any gesture like pinch/panning/tap. This I call it a "locked" state.
In this state, map does not respond to anything. Trying to rotate/pinch will "unlock" it. But trying to pan will not. So for example, the sequence is, I pan the map, it follows. Then I pan the map several times, it does not move at all. Then I try to rotate it, the map stays still. Then I pan it the third time, it can follow my gesture again.
So my question is, what is the difference between this two state, what changes happened to the map when I move/try to rotate it.
My code is very simple, just for test purpose. myGestureRecognizer.m :
#import "myGestureRecognizer.h"
#import <MapKit/MapKit.h>
#implementation myGestureRecogniczer
-(id)init;
{
self = [super init];
self.delegate = self;
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches Moved");
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches Canceled");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Touches Ended");
}
#end
And mapViewController.m :
-(void)viewDidLoad
{
[super viewDidLoad];
self.myMapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame))];
self.myMapView.delegate = self;
self.myMapView.calloutView.delegate = self;
[self.view addSubview:self.myMapView];
myGestureRecogniczer *gestureRecognizer = [[myGestureRecogniczer alloc] init];
[self.myMapView addGestureRecognizer:gestureRecognizer];
}
I'm making a game using Xcode for iOS. This is a segment of code I have that makes the sprite jump up when the screen is tapped:
//tap/touch to jump (& play sound)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *) event{
[self playSound];
jumpUp = 16;
}
How can I implement it so that the sprite just keeps going up whist you are touching the screen instead of just a single tap?
//Pseudo code:
while touchingScreen {
jumpUp +=1;
}
You need to start some kind of loop in touchesBegan that runs while the touch is lasting. Then in touchesEnded (and cancelled!) make that loop stop. You can use a repeating NSTimer or something like the following. You may need to do some adjustments to make it smooth enough for a game.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// touching is a BOOL that keeps track of the touch event
// YES means that a touch is happening at the moment
touching = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:#selector(up) withObject:nil afterDelay:.0];
});
}
- (void)up {
// move your sprite further up
NSLog(#"up");
if (touching) {
// if the user is still touching repeat moving the sprite up
[self performSelector:#selector(up) withObject:nil afterDelay:0.1];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
// The finger was lifted, stop the up movement
touching = NO;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
touching = NO;
}
I see a lot of question about it on web, and particularly on StackOverflow.
I test many given answers but in my case, nothing works.
My class implements the protocol UIGestureRecognizerDelegate:
#interface CDMapViewController : CDViewController <UIGestureRecognizerDelegate>
The following method is wrote from xcode autocompletion in #implentation of my class
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch {
NSLog(#"not called");
return NO;
}
I have correctly init the UIGestureRecognizer in first method which correctly call the second, third and fourth methods:
- (void)initGestureOnMap {
UIGestureRecognizer *gestureRecognizer = [[UIGestureRecognizer alloc] init];
gestureRecognizer.delegate = self;
[self.view addGestureRecognizer:gestureRecognizer];
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
gesture_dragging = NO;
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
gesture_dragging = YES;
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if (gesture_dragging || [touches count] != 1) return;
/* bla bla bla */
}
... It doesn't log – not called ... why ?
You need to call super implementations for the touches methods.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
gesture_dragging = NO;
}
... and so on.
These methods need to be implemented on your view, and not your view controller.
Pick what kind of gesture you want. By itself, UIGestureRecognizer doesn't do much, so pick one like UITapGestureRecognizer. Next, implement your gesture recognizer with the designated initializer.
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myMethod:)];
Finally, implement myMethod:.
-(void)myMethod:(UITapGestureRecognizer *)recognizer
{
// Whatever this does.
}
I am trying to create a button subclass which colours a button (when it is selected) dependent on whether it is the correct answer to a multiple choice question.
I am doing this like so:
In my UIViewController:
- (IBAction)answerQuestion:(id)sender {
QGPrettyButton *answerButton;
if ([sender isKindOfClass:[UIButton class]]) {
answerButton = (QGPrettyButton *)sender;
answerButton.isCorrectAnswer = [self.quizGame checkAnswer:self.currentQuestion answer:answerButton.titleLabel.text];
[self performSelector:#selector(removeQuestion) withObject:nil afterDelay:TIME_TO_SHOW_CORRECT_ANSWER];
}
}
In the setter method for isCorrectAnswer:
- (void)setIsCorrectAnswer:(BOOL)isCorrectAnswer
{
self.selected = YES;
[self setNeedsDisplay];
[self performSelector:#selector(returnToUnselected) withObject:nil afterDelay:TIME_TO_SHOW_CORRECT_ANSWER];
_isCorrectAnswer = isCorrectAnswer;
}
- (void)returnToUnselected
{
self.selected = NO;
[self setNeedsDisplay];
}
And in my draw method for QGPrettyButton:
CGFloat hue = UNSELECTED_HUE;
CGFloat saturation = UNSELECTED_SATURATION;
if (self.state == UIControlStateHighlighted) {
hue = HOVERED_HUE;
saturation = HOVERED_SATURATION;
}
if (self.state == UIControlStateSelected) {
hue = self.isCorrectAnswer ? CORRECT_HUE : INCORRECT_HUE;
}
Overriding touch :
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
[self setNeedsDisplay];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self setNeedsDisplay];
[self performSelector:#selector(hesitateUpdate) withObject:nil afterDelay:0.1];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self setNeedsDisplay];
[self performSelector:#selector(hesitateUpdate) withObject:nil afterDelay:0.1];
}
This is all working fine if you mouse down on the button and release again, but if you tap it really quickly the IBAction is called but the colour of the button is not set and it just flashes quickly white then back to the unselected colour...
You can try the highlighted property of a UIView.
If you want it to be highlighted do:
self.highlighted = YES;
and vice versa.
You might try moving [self setNeedsDisplay] out of your touchesEnded:withEvent: and touchesCancelled:withEvent: methods and into your hesitateUpdate method.
I have been trying to figure out how to forward the touches from a UIScrollView to its superview. The reason is for sort of a cursor to follow the touch. Anyway, the scrollview is populated with UIButtons. My code for forwarding the touch is to use delegation in a scrollview subclass:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
if ([[self tfDelegate]respondsToSelector:#selector(tfDelegateBegan:)]) {
[[self tfDelegate]tfDelegateBegan:[touches anyObject]];
}
NSLog(#"touches began");
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved:touches withEvent:event];
if ([[self tfDelegate]respondsToSelector:#selector(tfDelegateMoved:)]) {
[[self tfDelegate]tfDelegateMoved:[touches anyObject]];
}
NSLog(#"touches moved");
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesEnded:touches withEvent:event];
if ([[self tfDelegate]respondsToSelector:#selector(tfDelegateEnded:)]) {
[[self tfDelegate]tfDelegateEnded:[touches anyObject]];
}
NSLog(#"touches ended");
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesCancelled:touches withEvent:event];
if ([[self tfDelegate]respondsToSelector:#selector(tfDelegateCancelled:)]) {
[[self tfDelegate]tfDelegateCancelled:[touches anyObject]];
}
NSLog(#"touches cancelled");
}
However, I have learned that UIScrollviews operate via UIGestureRecognizers, so these methods aren't even called by default. I realize that the gesture recognizers are exposed in iOS 5 but I need to support 4.0 as well. I did this instead:
NSArray *a = [[theScrollview gestureRecognizers]retain];
for (UIGestureRecognizer *rec in a) {
if ([rec isKindOfClass:[UIPanGestureRecognizer class]]) {
NSLog(#"this is the pan gesture");
rec.cancelsTouchesInView = NO;
}
}
This allows the gesture to work and the touches methods to be called simultaneously. The issue is, now if you try to scroll while touching a button, the button can be pressed while scrolling. Normally, the scroll cancels the button and the only time a button can be pressed is if the scrollview is not scrolling.
This is the desired functionality. Any suggestions on how I might achieve this?
Maybe try controlling the button action using a flag that would prevent the event from firing if the scroll view is scrolling.
BOOL isScrolling = NO;
- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {
isScrolling = YES;
}
- (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
isScrolling = NO;
}
- (void) didTapButton {
if(isScrolling)
return;
//Do Button Stuff
}