detecting touch in UIImageView animation - objective-c

i have a circle image that circles round the screen using a path animation. And i want to detect when the user touches the moving circle. However even though the image is moving round in a continuous circle its frame is still in the top left hand corner not moving, how can i update this so that i can detect a touch on the moving image? Here is the code...
Set Up Animation in ViewDidLoad:
//set up animation
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration = 10.0;
pathAnimation.repeatCount = 1000;
CGMutablePathRef curvedPath = CGPathCreateMutable();
//path as a circle
CGRect bounds = CGRectMake(60,170,200,200);
CGPathAddEllipseInRect(curvedPath, NULL, bounds);
//tell animation to use this path
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
//add subview
circleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ball.png"]];
[testView addSubview:circleView];
//animate
[circleView.layer addAnimation:pathAnimation forKey:#"moveTheSquare"];
Touches Method:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//detect touch
UITouch *theTouch = [touches anyObject];
//locate and assign touch location
CGPoint startPoint = [theTouch locationInView:self.view];
CGFloat x = startPoint.x;
CGFloat y = startPoint.y;
//create touch point
CGPoint touchPoint = CGPointMake(x, y);
//check to see if the touch is in the rect
if (CGRectContainsPoint(circleView.bounds, touchPoint)) {
NSLog(#"yes");
}
//check image view position
NSLog(#"frame x - %f, y - %f", circleView.frame.origin.x, circleView.frame.origin.y);
NSLog(#"center x - %f, y - %f", circleView.center.x, circleView.center.y);
NSLog(#"bounds x - %f, y - %f", circleView.bounds.origin.x, circleView.bounds.origin.y);
}
the imageview just seems to stay at the top left hand corner. I cant seem to figure out how to recognise if a touch gesture has been made on the moving ball.
any help would be appreciated,
Chris

You need to query the presentation layer of the view not it's frame. Only the presentation will be updated during the course of an animation...
[myImageView.layer presentationLayer]
Access the properties of this layer (origin, size etc) and determine if your touch point is within the bounds.

Related

Background Sprite Image keeps being centred on screen

I'm trying to implement a scrollable background into my current GameScene. This is supposed to be done via Gesture Recognition, which I'm already using for Taps and moving other scene objects.
Unlike pretty much every other result returned by my Google searches, I don't want an infinitely scrolling background. It just needs to move with your finger, and stay where it's been moved.
The Problem:
I can move the background SKSpriteNode in my scene, but as soon as I try to move it again it snaps to the center and your scrolling effectively becomes useless. It keeps resetting itself.
Here's what I've got so far for moving my Sprites:
-(void)selectTouchedNode:(CGPoint)location
{
SKSpriteNode *node = (SKSpriteNode *)[self nodeAtPoint:location];
if ([self.selectedNode isEqual:node]){
if (![self.selectedNode isEqual:self.background]){
self.selectedNode = NULL;
}
}
if ([node isKindOfClass:[SKLabelNode class]]){
self.selectedNode = node.parent;
} else {
self.selectedNode = node;
}
NSLog(#"Node Selected: %# | Position: %f, %f",node.name,node.position.x,node.position.y);
}
- (void)respondToPan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
// Get Touch Location in the View
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
// Convert that Touch Location
touchLocation = [self convertPointFromView:touchLocation];
// Select Node at said Location.
[self selectTouchedNode:touchLocation];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
// Get the translation being performed on the sprite.
CGPoint translation = [recognizer translationInView:recognizer.view];
// Copy to another CGPoint
translation = CGPointMake(translation.x, -translation.y);
// Translate the currently selected object
[self translateMotion:recognizer Translation:translation];
// Reset translation to zero.
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// Fetch Current Location in View
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
// Convert to location in game.
CGPoint correctLocation = [self convertPointFromView:touchLocation];
// If the selected node is the background node
if ([self.selectedNode isEqual:self.background]) {
NSLog(#"Scrolling the background: Node is: %#",self.selectedNode.name);
// Set up a scroll duration
float scrollDuration = 0.2;
// Get the new position based on what is allowed by the function
CGPoint newPos = [self backgroundPanPos:correctLocation];
NSLog(#"New Position: %f, %f",newPos.x,newPos.y);
// Remove all Actions from the background
[_selectedNode removeAllActions];
// Move the background to the new position with defined duration.
SKAction *moveTo = [SKAction moveTo:newPos duration:scrollDuration];
// SetTimingMode for a smoother transition
[moveTo setTimingMode:SKActionTimingEaseOut];
// Run the action
[_selectedNode runAction:moveTo];
} else {
// Otherwise, just put the damn node where the touch occured.
self.selectedNode.position = correctLocation;
}
}
}
// NEW PLAN: Kill myself
- (void)translateMotion:(UIGestureRecognizer *)recognizer Translation:(CGPoint)translation {
// Fetch Location being touched
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
// Convert to place in View
CGPoint location = [self convertPointFromView:touchLocation];
// Set node to that location
self.selectedNode.position = location;
}
- (CGPoint)backgroundPanPos:(CGPoint)newPos {
// Create a new point based on the touched location
CGPoint correctedPos = newPos;
return correctedPos;
}
What do I know so far?
I've tried printing the positions before the scrolling, when it ends, and when it gets initiated again.
Results are that the background does move positions, and once you try to move it again it starts at those new Coordinates, the screen has just repositioned itself over the centre of the sprite.
Supporting Illustration:
I'm not sure I 100% understand the situation you are describing, but I believe that it might be related to the anchor point of your background sprite node.
in your method:
- (void)translateMotion:(UIGestureRecognizer *)recognizer Translation:(CGPoint)translation
you have the line:
self.selectedNode.position = location;
Since your background sprite's anchor point is set to its center by default, any time you move a new touch, it will snap the background sprite's center to the location of your finger.
In other words, background.sprite.position sets the coordinates for the background sprite's anchor point (which by default is the center of the sprite), and in this case any time you set a new position, it is moving the center to that position.
The solution in this case would be to shift the anchor point of the background sprite to be directly under the touch each time, so you are changing the position of the background relative to the point of the background the touch started on.
It's a little hard to explain, so here's some sample code to show you:
1) Create a new project in Xcode using the Sprite Kit Game template
2) Replace the contents of GameScene.m with the following:
#import "GameScene.h"
#interface GameScene ()
#property (nonatomic, strong) SKSpriteNode *sprite;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
self.sprite = [SKSpriteNode spriteNodeWithImageNamed:#"Spaceship"];
self.sprite.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
[self addChild:self.sprite];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
UITouch *touch = [touches anyObject];
CGPoint nodelocation = [touch locationInNode:self.sprite];
//[self adjustAnchorPointForSprite:self.sprite toLocation:nodelocation];
CGPoint location = [touch locationInNode:self];
self.sprite.position = location;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
self.sprite.position = location;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
}
-(void)adjustAnchorPointForSprite:(SKSpriteNode *)sprite toLocation:(CGPoint)location {
// Remember the sprite's current position
CGPoint originalPosition = sprite.position;
// Convert the coordinates of the passed-in location to be relative to the bottom left of the sprite (instead of its current anchor point)
CGPoint adjustedNodeLocation = CGPointMake(sprite.anchorPoint.x * sprite.size.width + location.x, sprite.anchorPoint.y * sprite.size.height + location.y);
// Move the anchor point of the sprite to match the passed-in location
sprite.anchorPoint = CGPointMake(adjustedNodeLocation.x / self.sprite.size.width, adjustedNodeLocation.y / self.sprite.size.height);
// Undo any change of position caused by moving the anchor point
self.sprite.position = CGPointMake(sprite.position.x - (sprite.position.x - originalPosition.x), sprite.position.y - (sprite.position.y - originalPosition.y));
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
#end
3) Run the project in the sim or on a device and click / touch the top tip of the space ship and start dragging.
4) Notice that the spaceship snaps its center point to where your touch is. If you drag and release it to a new location, and the touch the screen again, it snaps its center back to your finger.
5) Now uncomment the line:
[self adjustAnchorPointForSprite:self.sprite toLocation:nodelocation];
and run the project again.
6) See how you can now drag the ship where you want it, and when you touch it later, it stays in place and follows your finger from the point you touched it. This is because the anchor point is now being adjusted to the point under the touch each time a new touch begins.
Hopefully this gives you a solution you can use in your game as well.

Check when SKShapeNode is clicked

So I generated an SKShapeNode, and need to know when that node is clicked. I do so by calling:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:positionInScene];
if ([node.name isEqualToString:TARGET_NAME]) {
// do whatever
}
}
}
So the result I'm getting is pretty weird. Clicking the dot itself does in fact work. However, pressing anywhere on the screen that is southwest of the SKShapeNode's position will also render the above code as true.
With the SKShapeNode represented by the red dot, any UITouch in the shaded region would render my code above as true.
Here is how I am building the SKShapeNode. It may also be important to note that my application runs in landscape mode.
#define RANDOM_NUMBER(min, max) (arc4random() % (max - min) + min)
- (SKShapeNode *)makeNodeWithName:(NSString *)name color:(UIColor *)color
{
SKShapeNode *circle = [SKShapeNode new];
int maxXCoord = self.frame.size.width;
int maxYCoord = self.frame.size.height;
CGFloat x = RANDOM_NUMBER((int)TARGET_RADIUS, (int)(maxXCoord - TARGET_RADIUS));
CGFloat y = RANDOM_NUMBER((int)TARGET_RADIUS, (int)(maxYCoord - TARGET_RADIUS - 15));
circle.fillColor = color;
circle.strokeColor = color;
circle.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x, y, TARGET_RADIUS, TARGET_RADIUS)].CGPath;
circle.name = name;
return circle;
}
Thanks for any help!
This happens because the circle node's position is at origin, and it draws the path in a rect starting at (x,y). So the node's frame is stretched to encompass everything between (0,0) to (x+TARGET_RADIUS, y+TARGET_RADIUS).
You can check this out for yourself, by visualizing the circle's frame:
SKSpriteNode *debugFrame = [SKSpriteNode spriteNodeWithColor:[NSColor yellowColor] size:circle.frame.size];
debugFrame.anchorPoint = CGPointMake(0, 0);
debugFrame.position = circle.frame.origin;
debugFrame.alpha = 0.5f;
[self addChild:test];
This reveals the actual clickable region (on OSX):
To fix your issue, try this:
circle.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-TARGET_RADIUS/2.0f, -TARGET_RADIUS/2.0f, TARGET_RADIUS, TARGET_RADIUS)].CGPath;
and add
circle.position = CGPointMake(x, y);

How to remove a sprite when it reaches a certain point or it is touched?

I am trying to make a game in cocos2d wherein I have sprites dropping from the top of the screen. Now, once the sprite is tapped, it should move back up. This works for me just fine but, there are some instances wherein the sprite would go back down after it goes off the screen. Also, my sprites don't disappear once they reach a certain y-axis position on the screen. Here's part of my code
-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint loc = [[CCDirector sharedDirector]convertToGL:[touch locationInView:[touch view]]];
CGRect rect = CGRectMake(sprite.position.x - (sprite.contentSize.width/2), sprite.position.y - (sprite.contentSize.height/2), sprite.contentSize.width, sprite.contentSize.height);
if(CGRectContainsPoint(rect, loc))
{
[sprite runAction:[CCCallFuncN actionWithTarget:self selector:#selector(spriteCaught)]];
}}
-(void)spriteCaught
{
//currentPos is an integer to get the current position of the sprite
id moveUp = [CCMoveTo actionWithDuration:1 position:ccp(currentPos, 500)];
[sprite runAction:[CCSequence actions:moveUp, nil]];
if(sprite.position.y >= 480)
{
[self removeChild:sprite cleanup:YES];
}}
Also, I'm not sure if my syntax is correct but my conditional statement (The one that checks the position in the y-axis of the sprite) doesn't work either. How do I go about fixing this? Any help and suggestion is greatly appreciated
Instead of manual rect, use sprite's bounding box.
if(CGRectContainsPoint([sprite boundingBox], loc))
Also update spriteCaught function.
-(void)spriteCaught
{
CGSize s = [[CCDirector sharedDirector] winSize];
float dest_y = s.height+sprite.contentSize.height*0.5f; //assumed ur sprite's anchor y = 0.5
//currentPos is an integer to get the current position of the sprite
id moveUp = [CCMoveTo actionWithDuration:1 position:ccp(sprite.position, dest_y)];
id calBlokc = [CCCallBlockN actionWithBlock:^(CCNode *node)
{
//here node = sprite tht runs this action
[node removeFromParentAndCleanup:YES];
}];
id sequence = [CCSequence actions:moveUp, calBlokc, nil];
[sprite runAction:sequence];
}

CALayer Draggable Mask

I have two images. Both of the same scene, of the same size, that take up the entire screen. One is image blurry, one is in focus. The desired effect is that the user will initially see the blurry image and as they drag their finger across the screen from left to right they will see the part of the image to the left of where they drag be in focus (for example if they drag only halfway across, then the left-half of the scene is the focused image while the right half is still blurry.
I'm doing this by subclassing UIImageView with the focused image as the image - then adding a CALayer of the blurry image with a mask applied, then changing the location of the mask according to touchesBegan/touchesMoved. The problem is that performance is very slow using the approach below. So I'm wondering what I'm doing wrong.
#interface DragMaskImageView : UIImageView
#end
#implementation DragMaskImageView{
BOOL userIsTouchingMask;
CALayer *maskingLayer;
CALayer *topLayer;
float horzDistanceOfTouchFromCenter;
}
- (void)awakeFromNib {
topLayer = [CALayer layer];
topLayer.contents = (id) [UIImage imageNamed:#"blurryImage.jpg"].CGImage;
topLayer.frame = CGRectMake(0, 0, 480, 300);
[self.layer addSublayer:topLayer];
maskingLayer = [CALayer layer];
maskingLayer.contents = (id) [UIImage imageNamed:#"maskImage.png"].CGImage;
maskingLayer.anchorPoint = CGPointMake(0.0, 0.0);
maskingLayer.bounds = CGRectMake(0, 0, 480, 300);
[topLayer setMask:maskingLayer];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchPoint = [[[event allTouches] anyObject] locationInView:self];
if (touchPoint.x < maskingLayer.frame.origin.x) {
NSLog(#"user is touching to the left of mask - disregard");
userIsTouchingMask = NO;
} else {
NSLog(#"user is touching ");
horzDistanceOfTouchFromCenter = touchPoint.x - maskingLayer.frame.origin.x;
userIsTouchingMask = YES;
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if (userIsTouchingMask) {
CGPoint touchPoint = [[[event allTouches] anyObject] locationInView:self];
float newMaskX = touchPoint.x - horzDistanceOfTouchFromCenter;
if (newMaskX < 0) {
newMaskX = 0;
}
if (newMaskX > 480) {
newMaskX = 480;
}
maskingLayer.frame = CGRectMake(newMaskX, 0 ,480, 300);
}
}
I checked the related thread core animation calayer mask animation performance but setting shouldRasterize to YES on any layer doesn't seem to help the performance problem.
I ran into the same problem, and updating the frame of the mask always lags behind the touch. I found that actually re-creating the mask with the new frame ensures that the mask is always updated instantly.
Perhaps the problem is that the layer's implicit animation is causing it to appear to be sluggish. Turning off the implicit animation should fix the problem:
[CATransaction begin];
[CATransaction setDisableActions:YES];
maskingLayer.frame = CGRectMake(newMaskX, 0 ,480, 300);
[CATransaction commit];
This is why #Rizwan's work-around works. It's a way of bypassing the implicit animation.

How to add movable UIView as subview with dynamic size at runtime in objective C?

I need to add a UIView as subview of a UIView where a UIImageview is displayed. What I need to implement is that when the image is loaded in my UIIMageview, at the same time I am showing the UIView call it as newView. In the beginning,I am showing it with static size say 190X170 and alpha as 0.5. Once i tap my finger on that view,it should move on the whole UIImageview. Once it is moved at the last,that coordinated I am taking and cropping my image with the same points. Now I am done with the moving part using the code below.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if (CGRectContainsPoint(newView.frame, touchLocation))
{
dragging = YES;
oldX = touchLocation.x;
oldY = touchLocation.y;
}
if (dragging)
{
newView.layer.borderColor = [UIColor clearColor].CGColor;
newView.layer.borderWidth = 1.0f;
CGRect frame = newView.frame;
frame.origin.x = newView.frame.origin.x + touchLocation.x - oldX;
x = frame.origin.x;
NSLog(#"x value : %d",x);
frame.origin.y = newView.frame.origin.y + touchLocation.y - oldY;
y = frame.origin.y;
NSLog(#"y value : %d",y);
newView.frame = frame;
}
oldX = touchLocation.x;
oldY = touchLocation.y;
}
What I need to implement is to resize the UIView on taping two fingers like that of UIScrollview. I tried to implement the UIScrollview but it is not giving me good effect. I tried to implement
NSUInteger resizingMask = NSViewHeightSizable | NSViewWidthSizable;
[self.view setAutoresizesSubviews:YES];
[newView setAutoresizingMask:resizingMask];
but in vain. Nothing is taking place.
Can anybody show me a path for the same. Thanks in advance.
To provide the zoom in zoom out feature you can use the UIPinchGuestureRecognizer. If you are using iOS 5 then it will be pretty easy for you.
here is the very cool tutorial for that hope it will help for you.
http://www.raywenderlich.com/6567/uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more