Neither touchesEnded nor touchesCancelled not called sometimes after touchesMoved - ios

I Have one circular view in my application and I am moving some objects like graph in that circular view its work fine in 90% cases but at some cases its not calling TouchEnded and my reset graphs code is in TouchEnded methods so its unloaded at some point below is my code for touches delegate methods.
#pragma mark - Touch Events For Rotation of GRAPH
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
prevAngle = [Utilities angleBetweenPoint:touchPoint toPoint:self.view.center];
/// convert negative angle into positive angle
if(prevAngle < 0){
prevAngle = PI_DOUBLE + prevAngle;
}
//
[_viewStaticRadialPart hideToolTip];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGFloat diffAngle, tempCurAngle, tempPrevAngle, curTransformAngle, newTransformAngle;
NSLog(#"touchesMoved");
// Get the only touch (multipleTouchEnabled is NO)
UITouch* touch = [touches anyObject];
// Track the touch
CGPoint touchPoint = [touch locationInView:self.view];
curAngle = [Utilities angleBetweenPoint:touchPoint toPoint:self.view.center];
/// convert negative angle into positive angle
if(curAngle < 0){
curAngle = PI_DOUBLE + curAngle;
}
prevAngle = curAngle;
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self resetViewsOnceRotationStops];
}
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
[resetViewsAfterTouchStops invalidate];
resetViewsAfterTouchStops = nil;
}
I am stuck here since last night so any help will be appreciated.
Thanks in advance.

I don't know the cause for this problem from core level.
But NSTimer can help us in
But I feel we can resolve this by adding setting / replace NSTimer instance while touchesMoved called and we can reset that timer on touchesEnded and touchesCancelled. So, in either case, your touchesEnded or touchesCancelled not called timer will do that job and your reset logic works as it should be expected.
Demo Source Code
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
NSLog(#"touchesMoved");
//Timer re-initialize code here
if (resetViewsAfterTouchStops) {
[resetViewsAfterTouchStops invalidate];
resetViewsAfterTouchStops = nil;
}
resetViewsAfterTouchStops = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(resetViewsOnceRotationStops) userInfo:nil repeats:NO];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
isTouched = NO;
[resetViewsAfterTouchStops invalidate];
resetViewsAfterTouchStops = nil;
[self resetViewsOnceRotationStops];
[self setUserInteractionOnSectorsAs:YES];
}
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
NSLog(#"touchesCancelled");
if(resetViewsAfterTouchStops)
{
[self resetViewsOnceRotationStops];
[self setUserInteractionOnSectorsAs:YES];
}
[resetViewsAfterTouchStops invalidate];
resetViewsAfterTouchStops = nil;
}
- (void) resetViewsOnceRotationStops
{
//your reset code will be place here
}

Try removing UIGestureRecognizer objects on that view if you are using any. UIGestureRecognizer objects interfere with touch events lifecycle.

Related

Cocos2d (IOS) - How do I constantly get the location of the user's touch?

How do I constantly get the location of the user's touch? I'm working on controllers for a game, and as it stands I must lift and individually place my finger on the new control in order to make it work. It seems that the touchLocation variable only updates upon each tap, and I want it to update constantly, so that even if I slide my finger around it will know the location and update it accordingly. Here is the relevant code. Thank you!
-(void)update:(CCTime)delta{
if([playerDirection isEqual:#"left"]){
_player.position = ccp(_player.position.x - 2, _player.position.y);
}
if([playerDirection isEqual:#"right"]){
_player.position = ccp(_player.position.x + 2, _player.position.y);
}
if([direction isEqual:#"up"]){
_enemy.position = ccp(_enemy.position.x, _enemy.position.y + 2);
}
if([direction isEqual:#"down"]){
_enemy.position = ccp(_enemy.position.x, _enemy.position.y - 2);
}
if(_enemy.position.y >= self.contentSize.height){
direction = #"down";
}
if(_enemy.position.y <= 2){
direction = #"up";
}
if(isTouching){
_player.position = ccp(_player.position.x + 2, _player.position.y);
}
}
// -----------------------------------------------------------------------
#pragma mark - Touch Handler
// -----------------------------------------------------------------------
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocation = [touch locationInView:[touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
if(touchLocation.x > 400){
playerDirection = #"right";
}
if(touchLocation.x < 200){
playerDirection = #"left";
}
CGPoint touchLoc = [touch locationInNode:self];
//isTouching = true;
// Log touch location
CCLOG(#"Move sprite to # %#",NSStringFromCGPoint(touchLoc));
// Move our sprite to touch location
//CCActionMoveTo *actionMove = [CCActionMoveTo actionWithDuration:1.0f position:touchLoc];
//[_sprite runAction:actionMove];
}
- (void)touchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
playerDirection = #"none";
}
i see you've got a touchesBegan and touchesEnded... but you're missing touchesMoved.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for(UITouch *touch in touches) {
//do whatever you want
}
}
Hope that helps, good luck!
To get Touch you have to enable touch in -(id) init() method.
you can enable touch by
self.IsTouchEnable = YES
and after this you can get touch in following touch handling methods
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
hope This will help you

What does userInteractionEnabled = NO mean and how does it work?

Cocos 2d-iphone 3.0. I am using this code to detect whether a single sprite is touched
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [touch locationInView: [touch view]];
CGPoint convertedlocation = [[CCDirector sharedDirector] convertToGL: location];
CGPoint convertedNodeSpacePoint = [self convertToNodeSpace:convertedlocation];
if (CGRectContainsPoint([_sprite boundingBox],convertedNodeSpacePoint))
{
// Remove sprite
}
}
I have some code inside that should remove some other sprites from parent. Logically, when _sprite
is touched second time, application will crash, because other sprites'v been already removed.
I was trying to make _sprite untouchable using _sprite.userInteractionEnabled = NO;, but this has no effect.
What does userInteractionEnabled mean exactly and how would one use this to facilitate touch detection on sprites.
What is the optimal way of handling touches on sprites in my scene?
There is no way to disable interactions on a ccsprite. What you should do is:
bool firstTimeClicked=false;
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
if(firstTimeClicked==true)
{
return NO;
}
CGPoint location = [touch locationInView: [touch view]];
CGPoint convertedlocation = [[CCDirector sharedDirector] convertToGL: location];
CGPoint convertedNodeSpacePoint = [self convertToNodeSpace:convertedlocation];
if (CGRectContainsPoint([_sprite boundingBox],convertedNodeSpacePoint)) {
firstTimeClicked=true;
}
}
What userInteractionEnabled does
When you set userInteractionEnabled=YES on a sprite that mean that user interaction callbacks are going to get called.
So for example in your scene if you would have userInteractionEnabled=NO your -(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event would not get called.
What you we're doing
You were detecting the touches in your scene. And then you we're checking what was under that touch location, so if anything was under there it would be detected.
How to accomplish this properly - subclass your CCSprite
You want to handle these kind of behaviours in custom CCSprite classes, because if not your scenes touchBegan is going to turn into a huge buggy if else switch statement.
Believe me, I have been there.
Here is a code example of what this class could look like :
The .h file
#interface SubclassedSprite : CCSprite
#end
And the .m file
#implementation SubclassedSprite
- (void) onEnter
{
[super onEnter];
self.userInteractionEnabled = YES;
}
- (void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CCLOG(#"Touch began on sprite");
// Your code when the Sprite was touched [self removeFromParentAndCleanup:YES];
}
#end
This is the proper, scalable way to handle this.
Below code is without taking an extra BOOL variable and it should work for you:
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [touch locationInView: [touch view]];
CGPoint convertedlocation = [[CCDirector sharedDirector] convertToGL: location];
CGPoint convertedNodeSpacePoint = [self convertToNodeSpace:convertedlocation];
if (CGRectContainsPoint([_sprite boundingBox],convertedNodeSpacePoint)) {
if(_sprite.userInteractionEnabled)//Here you can remove the children of _sprite. e.g.
{
//Here run some action or do what you want to do. For e.g.
CCFadeTo *fadeOut = [CCFadeTo actionWithDuration:0.5f opacity:0];
CCCallfuncN *clean = [CCCallFuncN actionWithTarget:self SEL:#selector(cleanSpriteChildren:)];
for(id item in _sprite.children)
if(item)
[item runAction:[CCSequence runActions:fadeOut, clean]];
}
else{//Do your own task while _sprite is untouchable}
}
}
-(void)cleanSpriteChildren:(id)sender
{
[item removeFromParentAndCleanUp:YES]
if(([_sprite children] count) == 0)
_sprite.userInteractionEnabled = YES; //Here make the _sprite touchable again
}

touchesEnd does not get called bit touchesBegin and touchesMoved does get called

I'm trying to do a drag in ios. If the drag movement was a short distance, I was going to count the drag as a click event, by comparing the start x,y (from touchesBegan) to the x,y in touchesEnd.
It seems like touchesEnd never gets called. I put a break point to verify it and the break point never went off.
code:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSUInteger touchCount = [touches count];
NSUInteger tapCount = [[touches anyObject] tapCount];
[ self UpdateDrag];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSUInteger touchCount = [touches count];
NSUInteger tapCount = [[touches anyObject] tapCount];
}
- (void) touchesEnd:(NSSet *)touches withEvent:(UIEvent *)event {
// this methes never gets called
NSUInteger touchCount = [touches count];
NSUInteger tapCount = [[touches anyObject] tapCount];
if (mDisplay==nil)
{
mDisplay = [[cDisplayYesOrNo_iPhone alloc]
initWithNibName:#"cDisplayYesOrNo_iPhone"
bundle:[NSBundle mainBundle]];
}
[ mDisplay SetUp];
[self presentModalViewController: mDisplay animated:NO];
}
Is - (void)touchesEnded: not - (void)touchesEnd:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIResponder_Class/Reference/Reference.html#//apple_ref/occ/instm/UIResponder/touchesEnded:withEvent:
You need to edit touchesEnd to touchesEnded.
Also, for completeness, you need to consider whether you also need to respond to touchesCancelled which is called instead of touchesEnded in some circumstances.

cocos2d for iphone touch detection on a sprite

I am using ccTouchesBegan and ccTouchesEnded methods to register touches. Everything was ok until I placed some buttons(ccmenuitems) on my node. now when i place my finger down on a button and then move it to any other place of screen ccTouchesEnded method does not calls. What am I doing wrong? How can I detect touches on a button?
some code:
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [self convertTouchToNodeSpace: [touches anyObject]];
// here i check if touch is in the right place
if ([self ptInRect:location :CGRectMake(0, center.y - 160, winSize.width, 40)]) {
dragBeginLocation = location;
}
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint location = [self convertTouchToNodeSpace: [touches anyObject]];
if (ABS(location.x - dragBeginLocation.x) < 8) {
NSLog(#"TAP");
} else {
NSLog(#"SWIPE");
}
}
So, when I begin touch on a sprite and release on background I get nothing in console.
Buttons are in CCMenu, which is higher than background.

Detect only double or single tap with UIViews?

I want to detect JUST double / single tap when the user touches a view.
I made something like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint prevLoc = [touch ]
if(touch.tapCount == 2)
NSLog(#"tapCount 2");
else if(touch.tapCount == 1)
NSLog(#"tapCount 1");
}
But it always detect 1 tap before 2 taps. How can I detect just 1 / 2 tap?
Thanks for you help. I also found a way like that:
-(void)handleSingleTap
{
NSLog(#"tapCount 1");
}
-(void)handleDoubleTap
{
NSLog(#"tapCount 2");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSUInteger numTaps = [[touches anyObject] tapCount];
float delay = 0.2;
if (numTaps < 2)
{
[self performSelector:#selector(handleSingleTap) withObject:nil afterDelay:delay ];
[self.nextResponder touchesEnded:touches withEvent:event];
}
else if(numTaps == 2)
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(handleDoubleTap) withObject:nil afterDelay:delay ];
}
}
It will help to define methods for single and double taps
(void) handleSingleTap {}
(void) handleDoubleTap {}
So then in touchesEnded you can call the appropriate method based on the number of taps, but only call handleSingleTap after a delay to make sure double tap has not been executed:
-(void) touchesEnded(NSSet *)touches withEvent:(UIEvent *)event {
if ([touch tapCount] == 1) {
[self performSelector:#selector(handleSingleTap) withObject:nil
afterDelay:0.3]; //delay of 0.3 seconds
} else if([touch tapCount] == 2) {
[self handleDoubleTap];
}
}
In touchesBegan, cancel all requests to handleSingleTap so that the second tap cancels the first tap's call to handleSingleTap and only handleDoubleTap will be called
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(handleSingleTap) object:nil];
Maby you can use some time interval. Wait with dispatching the event for (x)ms. If you get two taps in that time-period, dispatch a double-tap. If you only get one- dispatch single tap.

Resources