Carrying a touch event from beginning to end in Sprite Kit - objective-c

I am trying to create an app that creates a sprite node when the touch begins, drags it when the user's finger moves, and drops it when the touch has ended. What I have now simply creates a new sprite for every new touch location:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *gamePiece = [self pickObject];
gamePiece.position = location;
gamePiece.physicsBody.dynamic = NO;
[self addChild:gamePiece];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *gamePiece = [self pickObject];
gamePiece.position = location;
gamePiece.physicsBody.dynamic = NO;
[self addChild:gamePiece];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *gamePiece = [self pickObject];
gamePiece.position = location;
gamePiece.physicsBody.dynamic = NO;
[self addChild:gamePiece];
}
}
How do I carry the same, single sprite node across the touch methods? I have searched far and wide for tutorials, but nothing seems to fit exactly what I need.

You can do so by maintaining an instance variable for the touch as well as the SKSpriteNode associdated with that touch.
#implementation MyScene
{
UITouch *currentTouch;
SKSpriteNode *currentGamePiece;
}
Then, modify the touch delegates as follows:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *gamePiece = [self pickObject];
gamePiece.position = location;
gamePiece.physicsBody.dynamic = NO;
[self addChild:gamePiece];
currentTouch = touch;
currentGamePiece = gamePiece;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
if ([touch isEqual:currentTouch])
{
currentGamePiece.position = location;
}
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches)
{
CGPoint location = [touch locationInNode:self];
if ([touch isEqual:currentTouch])
{
currentGamePiece.position = location;
currentGamePiece = nil;
currentTouch = nil;
}
}

Related

Changing SKSpriteNode property via touchesBegan

I was wondering if there was a more efficient way to changing the property of a SKSpriteNode in touchesBegan than my current method. The following is my method:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"Start"]){
for (SKSpriteNode *node2 in [self children]){
if ([node2.name isEqualToString:#"Start"]){
node2.texture = [SKTexture textureWithImageNamed:#"button_beige_pressed"];
}
}
}
...
}
Your current logic suggest that you have multiple nodes with the name 'start'. If this is actually the case I suggest creating an array and storing all 'start' nodes in said array.
If however you only have a single node with the name 'start', then no loop is needed as you already have a reference to the node with the touch function.
I found a solution:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKSpriteNode *node = (SKSpriteNode *)[self nodeAtPoint:location];
if ([node.name isEqualToString:#"Start"]){
node.texture = startPressed;
}
}
I was using a Node when I should have been using a spritenode.

crashed when touch two sprites at the same time(cocos2d-iphone)

The app will crash when two sprites are touched at the same time.
-(void)addEnemy
{
enemy = [CCSprite spriteWithFile:#"enemy.png"];
enemy.position = ccp(winsize.width / 2, winsize.height / 2);
[spriteSheet addChild:enemy];
[spritetiles addObject:enemy]; //spritetiles is NSMutableArray
}
touch code
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [self convertTouchToNodeSpace: touch];
for (CCSprite *target in [spriteSheet children]) {
if (CGRectContainsPoint(target.boundingBox, location)) {
[target stopAllActions];
[spriteSheet removeChild:target cleanup:YES];
[spritetiles removeObject:target];
}
}
}
if I touch any one of the sprite, there's no error, but if i touch two sprites(sometime some sprites' position is nearby), the app will crash, at the code line "if (CGRectContainsPoint(target.boundingBox, location)) {", so how can I fix it? thanks
Updated
Use reverseEnumerator in order to iterate through an array when you may need to remove elements as part of the for loop:
-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [self convertTouchToNodeSpace: touch];
for (CCSprite *target in [spriteSheet.children reverseObjectEnumerator]) {
if (CGRectContainsPoint(target.boundingBox, location)) {
[target stopAllActions];
[spriteSheet removeChild:target cleanup:YES];
[spritetiles removeObject:target];
}
}
}

How to drag and move UITextFields?

I have it set up where i can move multiple labels. I'm having problems moving a UITextfield. I made sure that user interaction and multiple touch is enabled.
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if ([touch view] == tex1) {
tex1.center = location;
Try to make this trick, it works for me
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *clearView = [[UIView alloc] initWithFrame:tex1.frame];
[clearView setTag:101];
[self.view addSubview:clearView];
[clearView release];
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
if ([[touch view] tag] == 101 && [touch tapCount] == 2) {
[tex1 becomeFirstResponder];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.view];
if ([[touch view] tag] == 101) {
tex1.center = location;
[[touch view] setCenter:location];
}
}

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
}

how to let the touchesMoved only control one view?

i create an UIButton on the view,and i want to let the touchesMoved only control the UIButton,not the whole view
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchMoved = [touch locationInView:self.view];
}
i want to do like this
if i touch the UIButton,and then the UIButton can be moved with my finger,if i touch other view and my finger is moving on the screen,the UIButton does nothing.
that means the function touchesMoved only has the role of the UIButton,so how can i do it? thanks
I presume the code you show takes place in your custom view controller subclass, and the UIButton is a subview of its view.
Define a simple BOOL in your class that you set to NO first. Then update it in the event handling methods.
// .h
BOOL buttonTouched;
// .m
// in the viewDidLoad
buttonTouched = NO;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// test wether the button is touched
UITouch *touch = [touches anyObject];
CGPoint touchBegan = [touch locationInView:self.view];
if(CGRectContainsPoint(theButton.frame, touchBegan) {
buttonTouched = YES;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(buttonTouched) {
// do it here
UITouch *touch = [touches anyObject];
CGPoint touchMoved = [touch locationInView:self.view];
CGRect newFrame = CGRectMake(touchMoved.x,
touchMoved.y,
theButton.frame.width,
theButton.frame.height);
theButton.frame = newFrame;
}
}
// when the event ends, put the BOOL back to NO
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
buttonTouched = NO;
}