Swiping a row in a tableview, clicking other, selects swiped - objective-c

I have a tableview, and whenever I swipe a row in section A and after that select a row in section B, it thinks I selected the swiped row in section A! I placed a breakpoint to verify and am 100% sure that it thinks it's that cell AND that it calls it when I select the row in section B.
By swipe I mean that you place your finger one some part of a cell, then drag it across (left or right, doesn't matter) and then release it. This doesn't call didSelectRowAtIndexPath, since it is not a tap.
Example:
Swipe on indexpath A.1
Tap on indexpath B.4
OS calls tableView:didSelectRowAtIndexPath: A.1
Am I doing something wrong? What could go wrong?
Full code that handles touches in the specific cells:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
RDLogString(#"(%p) Received touches began", self);
moveCount = 0;
UITouch * touch = [touches anyObject];
touchBegin = [touch locationInView: nil];
[[self nextResponder] touchesBegan: touches withEvent: event];
}
- (void) touchesMoved: (NSSet * const)touches withEvent:(UIEvent * const)event {
RDLogString(#"(%p) Received touches moved", self);
moveCount++;
[[self nextResponder] touchesMoved: touches withEvent: event];
}
- (void) touchesEnded: (NSSet * const)touches withEvent:(UIEvent * const)event {
RDLogString(#"(%p) Received touches ended", self);
if(![self checkUserSwipedWithTouches: touches]){
[[self nextResponder] touchesEnded: touches withEvent: event];
}
}
- (BOOL) checkUserSwipedWithTouches: (NSSet * const) touches {
CGPoint touchEnd = [[touches anyObject] locationInView: nil];
NSInteger distance = touchBegin.x - touchEnd.x;
// This code shows an animation if the user swiped
if(distance > SWIPED_HORIZONTAL_THRESHOLD){
[self userSwipedRightToLeft: YES];
return YES;
} else if (distance < (-SWIPED_HORIZONTAL_THRESHOLD)) {
[self userSwipedRightToLeft: NO];
return YES;
}
return NO;
}

I fixed it by, when a swipe was detected and handled, instead of not sending anything, I now send a touchesCancelled, which makes a lot of sense in retrospect I must admit, but it wasn't really clear that I should do that. I couldn't find proper documentation on what to do if you didn't want the action to be handled.
Code that works:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
moveCount = 0;
UITouch * touch = [touches anyObject];
touchBegin = [touch locationInView: nil];
[[self nextResponder] touchesBegan: touches withEvent: event];
}
- (void) touchesMoved: (NSSet * const)touches withEvent:(UIEvent * const)event {
moveCount++;
[[self nextResponder] touchesMoved: touches withEvent: event];
}
- (void) touchesEnded: (NSSet * const)touches withEvent:(UIEvent * const)event {
// If we DO NOT handle the touch, send touchesEnded
if(![self checkUserSwipedWithTouches: touches]){
[[self nextResponder] touchesEnded: touches withEvent: event];
} else { // If we DO handle the touch, send a touches cancelled.
self.selected = NO;
self.highlighted = NO;
[[self nextResponder] touchesCancelled: touches withEvent: event];
}
}

Related

SpriteKit SKAction by touching a Node

I want to run a SKAction if my SKSpriteNode Sorcerer is touched.
With my current code, whenever I touch, my SpriteNode 'stone' will move to the Sorcerer.
I could call the location of the touch with specific x and y coord., but since the 'Sorcerer' is moving, I can't do that.
How can I fix this?
- (void)Sorcerer {
Sorcerer = [SKSpriteNode spriteNodeWithImageNamed:#"Sorcerer.png"];
Sorcerer.size = CGSizeMake(85, 85);
Sorcerer.zPosition = 2;
Sorcerer.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(50, 50)];
Sorcerer.physicsBody.dynamic = NO;
Sorcerer.physicsBody.allowsRotation = NO;
Sorcerer.physicsBody.usesPreciseCollisionDetection = YES;
Sorcerer.physicsBody.restitution = 0;
Sorcerer.position = CGPointMake(self.frame.size.width * 1.25, self.frame.size.height / 2.2);
SKAction * actionMove = [SKAction moveToX:self.frame.size.width / 2 duration:10];
[Sorcerer runAction:[SKAction repeatActionForever:[SKAction sequence:#[actionMove]]]];
[self addChild:Sorcerer];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:Sorcerer];
Stone = [SKSpriteNode spriteNodeWithImageNamed:#"Stone.png"];
Stone.position = Human.position;
Stone.zPosition = 1;
Stone.scale = 0.6;
SKAction *action = [SKAction moveTo:Sorcerer.position duration:0.5];
SKAction *remove = [SKAction removeFromParent];
[Stone runAction:[SKAction sequence:#[action,remove]]];
[self addChild:Stone];
}
There are many situations when SKNode needs to inform its scene about some events or wants to force particular behavior only scene can provide.
Of course you can implement a touchesEnded: method in the scene and iterate through node but what if you have couple of thousands of them on the screen? The cleanest possible way of doing this is either by using delegation or notifications.
Delegation
We will create a protocol and set a scene as its delegate. Sourcerer will call delegate's method when it's touched. Let's define the protocol:
#class Sorcerer;
#protocol SorcererInteractionDelegate <NSObject>
- (void)sorcererTapped:(Sorcerer)sorcerer;
#end
Add a delegate property to Sorcerer class:
#interface Sorcerer : SKSpriteNode
#property (nonatomic, weak) id <SorcererInteractionDelegate> delegate;
#end
Finally, implement the touchesEnded: method.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if ([_delegate respondsToSelector:#selector(sorcererTapped:)])
{
[_delegate performSelector:#selector(sorcererTapped:)
withObject:self];
}
}
Remember to set userInteractionEnabled to YES in your init method of Sourcerer class.
When creating a sourcerer in your scene, set the delegate:
Sourcerer *sourcerer = ...
sourcerer.delegate = self;
and implement the sorcererTapped: method:
- (void)sorcererTapped:(Sorcerer)sorcerer;
{
// Do stuff with sorcerer.
}
Notifications
Delegation is cool but what if you need to inform several objects at once about the change. Then, you should use notifications.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UserDidTapSourcererNotification" object:self];
}
Your scene needs to register as an observer of #"UserDidTapSourcererNotification" notification and handle it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUserDidTapSourcererNotification:)
name:#"UserDidTapSourcererNotification";
object:nil];
- (void)handleUserDidTapSourcererNotification:(NSNotification *)notification
{
Sorcerer *sorcerer = (Sorcerer *)[notification object];
// Do stuff with sorcerer.
}
Hope it helps.
//give stone or Sorcere a name
through this name you can easily access that node
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
// decide if the node is of interest or can be ignored, something like this:
if ([node.name isEqualToString:#“stone”]) {
//apply action on stone
}
}

Get original touch location from UIPanGestureRecognizer

My view has a UIPanGestureRecognizer added to it.
The problem is that by the time the touch is recognized as a pan gesture, it has to be moved a few points, and I can't extract the original touch location at the UIGestureRecognizerStateBegan state.
In this state the translationInView: is (0,0), and any subsequent moves are calculated from this point, and not from the original.
Is there a way to extract the original location from the gesture recognizer itself, or do I need to override the touchesBegan: method?
You should be able to set the delegate for the gesture recogniser and implement
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
to get the initial touch.
You can solve this problem by implementing a custom PanGestureRecognizer which saves the original touch point and makes it available to the caller.
I went this route as I also wanted to control the distance moved to trigger a pan gesture.
This might be overkill for your situation, but works perfectly and is less difficult than it sounds.
To get the touchpoint coordinates, you just call:
[sender touchPointInView:yourView]
instead of:
[sender locationInView:yourView]
Here's the code for PanGestureRecognizer.m:
#import "PanGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#implementation PanGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
// touchPoint is defined as: #property (assign,nonatomic) CGPoint touchPoint;
self.touchPoint = [touch locationInView:nil];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:nil];
// customize the pan distance threshold
CGFloat dx = fabs(p.x-self.touchPoint.x);
CGFloat dy = fabs(p.y-self.touchPoint.y);
if ( dx > 2 || dy > 2) {
if (self.state == UIGestureRecognizerStatePossible) {
[self setState:UIGestureRecognizerStateBegan];
}
else {
[self setState:UIGestureRecognizerStateChanged];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if (self.state == UIGestureRecognizerStateChanged) {
[self setState:UIGestureRecognizerStateEnded];
}
else {
[self setState:UIGestureRecognizerStateCancelled];
}
}
- (void) reset
{
}
// this returns the original touch point
- (CGPoint) touchPointInView:(UIView *)view
{
CGPoint p = [view convertPoint:self.touchPoint fromView:nil];
return p;
}
#end
If you use locationInView: rather than translationInView: you will get absolute coordinates instead of relative coordinates. However you do have to start the pan to get input... To work around this your view can be a UIButton and you can trigger a - (IBAction)buttonWasTouched:(UIButton *)button forEvent:(UIEvent *)event connected to "touch down" like so:
-(IBAction)shakeIntensityPanGesturebuttonWasTouched:(UIButton *)button forEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view]; //touch location local cordinates
}
You can use the UIGestureRecognizerState
- (void) onPanGesture:(UIPanGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateBegan) {
origin = [sender locationInView];
} else {
current = [sender locationInView];
// Initial touch stored in origin
}
}
Swift (ish):
var state = recognizer.state
if state == UIGestureRecognizerState.Began {
println(recognizer.locationInView(viewForGesture))
//this will be the point of first touch
}

UIScrollview getting touch events

How can I detect touch points in my UIScrollView? The touches delegate methods are not working.
Set up a tap gesture recognizer:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured:)];
[scrollView addGestureRecognizer:singleTap];
and you will get the touches in:
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
CGPoint touchPoint=[gesture locationInView:scrollView];
}
You can make your own UIScrollview subclass and then you can implement the following:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"DEBUG: Touches began" );
UITouch *touch = [[event allTouches] anyObject];
[super touchesBegan:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"DEBUG: Touches cancelled");
// Will be called if something happens - like the phone rings
UITouch *touch = [[event allTouches] anyObject];
[super touchesCancelled:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"DEBUG: Touches moved" );
UITouch *touch = [[event allTouches] anyObject];
[super touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"DEBUG: Touches ending" );
//Get all the touches.
NSSet *allTouches = [event allTouches];
//Number of touches on the screen
switch ([allTouches count])
{
case 1:
{
//Get the first touch.
UITouch *touch = [[allTouches allObjects] objectAtIndex:0];
switch([touch tapCount])
{
case 1://Single tap
break;
case 2://Double tap.
break;
}
}
break;
}
[super touchesEnded:touches withEvent:event];
}
If we're talking about the points inside the scrollview then you can hook with the delegate method:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
and inside the method, read the property:
#property(nonatomic) CGPoint contentOffset
from the scrollView to get the coordination.
This works also on touch down event.
In the currently as correct marked answer you can get a touch point only at a "Tap" event. This event seems only to fire on "finger up" not on down.
From the comment of yuf in the same answer you can get the touch point from the underlying view also within the UIScrollView.
- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldReceiveTouch:(UITouch*)touch
{
CGPoint touchPoint = [touch locationInView:self.view];
return TRUE; // since we're only interested in the touchPoint
}
According to Apple's documentation the gestureRecognizer does:
Ask the delegate if a gesture recognizer should receive an object representing a touch.
which means to me that I can decide if a gestureRecognizer should recieve a touch or not.

Avoid UIView touch delay

I am using a subclass of UIView in my app. When the user touches on view I need to get the coordinates of the point; for this I am using:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
currentPoint=[[touches anyObject]locationInView:self];
}
It has some delay. Can any one give me the solution to get the points immediately? Thank you all.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
self.frame=frame;
rect=[[NSMutableArray alloc] initWithCapacity:1];
currentPointsList=[[NSMutableArray alloc]initWithCapacity:1];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
firstTouchedPoint = [touch locationInView:self];
NSLog(#"the firstTouched point is %");
for (int i=0; i<[rect count]; i++) {
if(CGRectContainsPoint([[rect objectAtIndex:i]CGRectValue], firstTouchedPoint)){
CGRect firstLineRect = CGRectMake(firstTouchedPoint.x,
[[rect objectAtIndex:i]CGRectValue].origin.y,
[[rect objectAtIndex:i]CGRectValue].size.width-firstTouchedPoint.x+[[rect objectAtIndex:i]CGRectValue].origin.x,
[[rect objectAtIndex:i]CGRectValue].size.height);
UIImageView *firstLine=[[UIImageView alloc]initWithFrame:firstLineRect];
firstLine.backgroundColor=[[UIColor blueColor]colorWithAlphaComponent:0.3f];
[self addSubview:firstLine];
break;
}
}
If you are using a UIScrollView there is a delay touches property (delaysContentTouches). You may want to make sure that is NO.

objective-c touch-events

I've got a set of Images and would like to know which I have touched. How could I implement that...?
To be more precise:
A "Home-Class" will instantiate a couple of Image-Classes:
Image *myImageView = [[Image alloc] initWithImage:myImage];
The image-class looks something like this:
- (id) initWithImage: (UIImage *) anImage
{
if ((self = [super initWithImage:anImage]))
{
self.userInteractionEnabled = YES;
}
return self;
}
later on, I use these touches-event-methods also in the Image-class:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{}
My problem at the moment: the touchesBegan/Ended methods will be fired no matter where I touched the screen, but I would like to find out which of the Images has been touched.....
Whenever you get the touch, you check if that touch happen in between your image area. Here is the example code, lets suppose you have UIImage object called img.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if (location.x >= img.x && location.x <= img.x && location.y >= img.y && location.y <= img.y) {
// your code here...
}
}
Inside your *.h (interface) file:
#interface MyViewController : UIViewController{
IBOutlet UIImageView *imageViewOne;
IBOutlet UIImageView *imageViewTwo;
UIImageView * alphaImage;
}
-(BOOL)isTouch:(UITouch *)touch WithinBoundsOf:(UIImageView *)imageView;
Place UIImageView components on your *.xib and bind them with 'imageViewOne' and 'imageViewTwo' using the "File's owner".
Go to the *.m (implementation) file and:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([self isTouch:touch WithinBoundsOf:imageViewOne])
{
NSLog(#"Fires first action...");
}
else if([self isTouch:touch WithinBoundsOf:imageViewTwo]){
NSLog(#"Fires second action...");
}
}
//(Optional 01) This is used to reset the transparency of the touched UIImageView
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[alphaImage setAlpha:1.0];
}
-(BOOL)isTouch:(UITouch *)touch WithinBoundsOf:(UIImageView *)imageView{
CGRect _frameRectangle=[imageView frame];
CGFloat _imageTop=_frameRectangle.origin.y;
CGFloat _imageLeft=_frameRectangle.origin.x;
CGFloat _imageRight=_frameRectangle.size.width+_imageLeft;
CGFloat _imageBottom=_frameRectangle.size.height+_imageTop;
CGPoint _touchPoint = [touch locationInView:self.view];
/*NSLog(#"image top %f",_imageTop);
NSLog(#"image bottom %f",_imageBottom);
NSLog(#"image left %f",_imageLeft);
NSLog(#"image right %f",_imageRight);
NSLog(#"touch happens at %f-%f",_touchPoint.x,_touchPoint.y);*/
if(_touchPoint.x>=_imageLeft &&
_touchPoint.x<=_imageRight &&
_touchPoint.y>=_imageTop &&
_touchPoint.y<=_imageBottom){
[imageView setAlpha:0.5];//optional 01 -adds a transparency changing effect
alphaImage=imageView;//optional 01 -marks the UIImageView which deals with the transparency changing effect for the moment.
return YES;
}else{
return NO;
}
}
That's how I handled that. I got the idea having read the post of "itsaboutcode".