ios drag and drop subview only working once - objective-c

I have UIView in which I want to be able to drag and drop subviews, and this is working fine once.
- (void)pan: (UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint startPos = [recognizer locationInView:self];
for (UIControl *sv in self.subviews){
if ([sv pointInside:startPos withEvent:nil]){
self.controlBeingDragged = sv;
}
}
}
if (((recognizer.state == UIGestureRecognizerStateChanged) ||
(recognizer.state == UIGestureRecognizerStateEnded)) &&
self.controlBeingDragged){
CGPoint translation = [recognizer translationInView:self];
self.controlBeingDragged.center = CGPointMake(self.controlBeingDragged.center.x + translation.x, self.controlBeingDragged.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:self];
if (recognizer.state == UIGestureRecognizerStateEnded){
self.controlBeingDragged = nil;
}
}
}
However, if I try to drag the same UIControl again, the containing view no longer knows where it is. I can drag it again by starting at the original position, so clearly there is something I am not doing to inform the containing view that its subview has moved. But what?

I'm surprised it's even working once. The pointInside:withEvent: message requires a point in the receiver's coordinate system, but you're sending a point that's in the receiver's superview's coordinate system. You can use convertPoint:toView: for each subview to convert the point to the subview's coordinate system before sending the point in the pointInside:withEvent: message.
CGPoint subviewPoint = [self convertPoint:startPos toView:sv];
if ([sv pointInside:subviewPoint withEvent:nil]) {
...
You might also want to check whether the -[UIView hitTest:withEvent:] message can replace the entire subview-searching loop.

Related

pan gesture issue, slight movement within UIButton detection

So I created this pan gesture recogniser to detect my touch on several UIBUttons. The idea is. I am looking for having the ability to slide my finger over all the buttons and trigger each and single one of them while I touch them. Right now I am able to slide over all the buttons and trigger the sound with this code. There is one problem that is occurring and that is when I replace the sound file with the NSLog statement, Every tiny little move I make with my finger within the same button keeps on repeating sound over and over again really fast. It reacts to the slightest movement.
How can I enable only hearing the sound one time after my finger touches the button and have the ability to play the same sound again when my finger touches the same button again. Pretty much the effect you get when you touch or slide your finger over a real piano.
Can anyone help me out with this?
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:pan];
}
//Method to handle the pan:
-(void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
//create a CGpoint so you know where you are touching
CGPoint touchPoint = [gesture locationInView:self.view];
//just to show you where you are touching...
NSLog(#"%#", NSStringFromCGPoint(touchPoint));
//check your button frame's individually to see if you are touching inside it
if (CGRectContainsPoint(self.button1.frame, touchPoint))
{
NSLog(#"you're panning button1");
}
else if(CGRectContainsPoint(self.button2.frame, touchPoint))
{
NSLog(#"you're panning button2");
}
else if (CGRectContainsPoint(self.button3.frame, touchPoint))
{
NSLog(#"you're panning button3");
}
Keep an NSMutableArray for detecting which sounds have been played since the last touch down event (pseudo code follows, please replace with proper method names and signatures):
NSMutableArray *myPlayedSounds;
void touchDown:(UITouch *) touch
{
//Empty played sounds list as soon as a touch event is sensed
[myPlayedSounds removeAllObjects];
}
-(void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
//create a CGpoint so you know where you are touching
CGPoint touchPoint = [gesture locationInView:self.view];
//just to show you where you are touching...
NSLog(#"%#", NSStringFromCGPoint(touchPoint));
//check your button frame's individually to see if you are touching inside it
if (CGRectContainsPoint(self.button1.frame, touchPoint) && [myPlayedSounds containsObject:#"button1"] == NO)
{
NSLog(#"you're panning button1");
[myPlayedSounds addObject:#"button1"];
}
else if(CGRectContainsPoint(self.button2.frame, touchPoint) && [myPlayedSounds containsObject:#"button2"] == NO)
{
NSLog(#"you're panning button2");
[myPlayedSounds addObject:#"button2"];
}
else if (CGRectContainsPoint(self.button3.frame, touchPoint) && [myPlayedSounds containsObject:#"button3"] == NO)
{
NSLog(#"you're panning button3");
[myPlayedSounds addObject:#"button3"];
}
}

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.

How do I use two actions in a UIPanGestureRecognizer?

I am working with two subviews. Each will be unique and have it's own "action".
Subview 1 = User can drag around the view, rotate, and zoom it
Subview 2 = When user moves finger across their screen an image is added at each point their finger touches.
I have both of these completed by using UIPanGestureRecognizer. My question is, how can I separate these two actions? I want to be able to add one subview, do what is required, and then when I add the other subview, prevent the previous actions from occurring.
Here is what I have tried, this is done in my panGesture method:
for (UIView * subview in imageView.subviews)
{
if ([subview isKindOfClass:[UIImageView class]])
{
if (subview == _aImageView)
{
CGPoint translation = [panRecognizer translationInView:self.view];
CGPoint imageViewPosition = _aImageView.center;
imageViewPosition.x += translation.x;
imageViewPosition.y += translation.y;
_aImageView.center = imageViewPosition;
[panRecognizer setTranslation:CGPointZero inView:self.view];
}
else if (subview == _bImageView)
{
currentTouch = [panRecognizer locationInView:self.view];
CGFloat distance = [self distanceFromPoint:currentTouch ToPoint:prev_touchPoint];
accumulatedDistance += distance;
CGFloat fixedDistance = 60;
if ([self distanceFromPoint:currentTouch ToPoint:prev_touchPoint] > fixedDistance)
{
[self addbImage];
prev_touchPoint = currentTouch;
}
}
}
}
If you want different gesture recognition in two different views, put separate recognizers on each view.
Usually, you want to have your view controller own and manage gesture recognizers, e.g.
- (void)viewDidLoad {
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
self.panGesture.delegate = self;
[self.viewX addGestureRecognizer:self.panGesture];
// repeat with other recognisers...
}
Note that setting your controller as delegate of the gestureRecognizer is important: this enables you to handle the following delegate method from the view controller (which was the main question):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// handle your logic, which gestureRecognizer should proceed...
return NO;
}
The handler method is the same is this example, but you can set up your own handlers as you like:
- (void)handleGesture:(UIGestureRecognizer*)gestureRecognizer {
// handle gesture (usually sorted by state), e.g.
// if(gesture.state == UIGestureRecognizerStateEnded) { ... }
}

Moving UIImageViews in ScrollView

In my scrollview im having multiple imageviews where i can able to move each imageviews when user taps LONGPRESS i have done this using,
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveImage:)];
[panGesture setMaximumNumberOfTouches:2];
[panGesture setDelegate:self];
[imageview addGestureRecognizer:panGesture];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] init];
[imageview addGestureRecognizer:longPressRecognizer];
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (void)adjustAnchorPointForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizerv {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
UIView *piece = gestureRecognizerv.view;
CGPoint locationInView = [gestureRecognizerv locationInView:piece];
CGPoint locationInSuperview = [gestureRecognizerv locationInView:piece.superview];
piece.layer.anchorPoint = CGPointMake(locationInView.x / piece.bounds.size.width, locationInView.y / piece.bounds.size.height);
piece.center = locationInSuperview;
}
}
- (void)moveImage:(UIPanGestureRecognizer *)gestureRecognizerg
{
NSLog(#"sdfgdsgsdg");
UIView *piece = [gestureRecognizerg view];
[self adjustAnchorPointForGestureRecognizer:gestureRecognizerg];
// need to test if the scrollview is already using the touches. If it is, leave them
if (!galleryView.tracking) {
if ([gestureRecognizerg state] == UIGestureRecognizerStateBegan || [gestureRecognizerg state] == UIGestureRecognizerStateChanged) {
CGPoint translation = [gestureRecognizerg translationInView:[piece superview]];
[piece setCenter:CGPointMake([piece center].x + translation.x, [piece center].y + translation.y)];
[gestureRecognizerg setTranslation:CGPointZero inView:[piece superview]];
}
}
}
Here my problem is the THE IMAGEVIEW is moving every where in scollview i need to rearrange the views i.e like Gallery view.
To get that rearrangement behavior you're going to need to have some logic that sets up a grid and 'snaps' elements to it, moves elements out the way when required, etc. It is reasonably complicated. Your current code simply moves views with your gestures.
However, if you are able to target iOS 6 and above you will almost certainly want to use a UICollectionView instead, which gives you a lot of this for free. Collection views simplify the layout and logic behind re-arranging, and will generally make your life a lot easier.

how to handle swipe gestures from the bottom of the screen

i have an application with three screens and i want to display and hide a small uiview when the user swipes from the bottom of the ipad screen towards up. I know that this cannot be down with normal swipe gestures. I was wondering if you could tell me how to handle these kinds of swipe gestures?
Add UIPanGestureRecognizer to your view.
-(void) panDetected:(UIGestureRecognizer *) gesture
{
BOOL fromBottom = NO;
CGPoint loc = [gesture locationInView:self.view];
if(gesture.state == UIGestureRecognizerStateBegan)
{
if(loc is somewhere in the bottom of view)
fromBottom = YES;
}
else if(gesture.state == UIGestureRecognizerStateChanged)
{
// You can up your view with finger movement here
}
else if(gesture.state == UIGestureRecognizerStateEnded)
{
}
}