I think the title is pretty self explaining. I need an icon that is dragged by users touch. If this icon is droped on a certain area I would like to call a function. How´s that possible?
Thanks a lot,
Tyler
Add UIPanGesture to the View.
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *pangesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(imageIsMoved:)];
pangesture.minimumNumberOfTouches = 1;
[self.myView addGestureRecognizer:pangesture];
}
Write a gesture method when drag is over
-(void)imageIsMoved:(UIPanGestureRecognizer *)gesture{
CGRect frameToBeCompared;
if (gesture.state == UIGestureRecognizerStateEnded) {
UIView *v = [gesture view];
CGRect viewFrame = v.frame;
if (CGRectEqualToRect(frameToBeCompared, viewFrame)) {
[self callMyMethod];
}
}
}
Check out this thread. Once you have the basics down, you can use an if statement to check the bounds of your icon and if they hit a specific spot call your next function.
Here's another example from one of my favorite iOS tutorial sites, www.raywenderlich.com
Related
I'm working on a GLKViewController based game which interprets taps and swipes as game controls. I want to support two-player mode by letting the first player tap or swipe on the left side of the screen, and letting the second player tap or swipe on the right side of the screen. In a perfect world, I'd like the gesture recognizers to work even if the swipes are sloppy and go past the centerline of the screen (with the starting point of the swipe being used to determine which player gets the input).
What would be the best way to implement this? Can I lay down a gesture recognizer on the left half of the screen, and another one on the right side of the screen? Will two separate recognizers work properly together even if both sides are being tapped/swiped rapidly at the same time? Or should I create a full-screen recognizer and parse the swipes and taps entirely on my own? I don't have experience with gesture recognizers so I don't know what the preferred approach is or how well they work when you have more than one being swiped on simultaneously.
I ended up making two UIViews overlaid on top of my GLKView, one on the left side of the screen and one on the right. Each view has a UIPanGestureRecognizer and a UILongPressGestureRecognizer (the long-press recognizer is basically a more flexible tap—I needed to use it to reject some gestures from being interpreted both as a pan and a tap at the same time). This worked magnificantly.
- (void)viewDidLoad
{
[super viewDidLoad];
// Add tap and pan gesture recognizers to handle game input.
{
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleLeftSidePan:)];
panRecognizer.delegate = self;
[self.leftSideView addGestureRecognizer:panRecognizer];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLeftSideLongPress:)];
longPressRecognizer.delegate = self;
longPressRecognizer.minimumPressDuration = 0.0;
[self.leftSideView addGestureRecognizer:longPressRecognizer];
}
{
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightSidePan:)];
panRecognizer.delegate = self;
[self.rightSideView addGestureRecognizer:panRecognizer];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightSideLongPress:)];
longPressRecognizer.delegate = self;
longPressRecognizer.minimumPressDuration = 0.0;
[self.rightSideView addGestureRecognizer:longPressRecognizer];
}
}
- (void)handleLeftSidePan:(UIPanGestureRecognizer *)panRecognizer
{
[self handleGameScreenPan:panRecognizer withVirtualController:&g_iPadVirtualController[0]];
}
- (void)handleRightSidePan:(UIPanGestureRecognizer *)panRecognizer
{
[self handleGameScreenPan:panRecognizer withVirtualController:&g_iPadVirtualController[1]];
}
- (void)handleLeftSideLongPress:(UILongPressGestureRecognizer *)longPressRecognizer
{
[self handleGameScreenLongPress:longPressRecognizer withVirtualController:&g_iPadVirtualController[0]];
}
- (void)handleRightSideLongPress:(UILongPressGestureRecognizer *)longPressRecognizer
{
[self handleGameScreenLongPress:longPressRecognizer withVirtualController:&g_iPadVirtualController[1]];
}
How do I use UIPanGestureRecognizer for moving multiple controls in the screen and how to identify which control in currently moving.
Example:
I'm using UITextField, UIButton and UIImageView in a view. I'm able to move multiple controls by using the different instantiate for each control but the method for panning is same method.
Please help me find out which control currently I'm panning in runtime.
1)Here is how you can set the pag gesture
- (void)setupGestures
{
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(movePanel:)];
[panRecognizer setMinimumNumberOfTouches:1];
[panRecognizer setMaximumNumberOfTouches:1];
[panRecognizer setDelegate:self];
[self.view addGestureRecognizer:panRecognizer];
}
2)This method gets called whenever the view moves
-(void)movePanel:(id)sender
{
CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view];
//here are different states from which you can change view
if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded)
{
}
if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateChanged) {
// Are you more than halfway? If so, show the panel when done dragging by setting this value to YES (1).
// Here you can change the position of the component depending on translation point
// translatedPoint.x & translatedPoint.y keep on changing as you move view over screen
}
}
3)For checking which view is moving you can check the view by tag & identify the view from that tag & change the frame
Ok so I am helping convert an android game to iOS. The game is based on 2048, but with letters instead of numbers. I have a good bit of it working but am still learning Objective C/iOS quirks. So far I have the tiles/grid working, movement is working, etc but I need a bit of help. The goal is to allow the user to long-press on a tile to select it, then slide their finger to an adjacent tile to begin spelling a word. I have the long press portion implemented but I'm at a bit of a loss on how to get it to long-press then swipe. On top of this I already have a swipe that allows the user to move the tiles. In searching on here I've seen suggestions about subclassing so I am figuring I need to subclass the UISwipeGestureRecognizer method. I already put in the simultaneously gesture recognizer, but am unsure where to go from here.
So, there are several questions to this.
What would be the best way to do this? Implement a subclass of each UISwipeGestureRecognizer?
Will my current swipe detection interfere? (right now a swipe by itself moves tiles in direction of swipe)
I would guess I need to do a (if long press) then activate subclassed swipe methods?
Any examples to answer the above questions would be of great help. I'm not asking you to do it for me but at least point me in a general direction. Thanks!
Code below.
// Grid.m
#import "Grid.h"
#import "Tile.h"
- (void)didLoadFromCCB {
// listen for swipes to the left
UISwipeGestureRecognizer * swipeLeft= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeLeft)];
swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
[[[CCDirector sharedDirector] view] addGestureRecognizer:swipeLeft];
// listen for swipes to the right
UISwipeGestureRecognizer * swipeRight= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeRight)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
[[[CCDirector sharedDirector] view] addGestureRecognizer:swipeRight];
// listen for swipes up
UISwipeGestureRecognizer * swipeUp= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeUp)];
swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
[[[CCDirector sharedDirector] view] addGestureRecognizer:swipeUp];
// listen for swipes down
UISwipeGestureRecognizer * swipeDown= [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeDown)];
swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
[[[CCDirector sharedDirector] view] addGestureRecognizer:swipeDown];
// listen for long press
UILongPressGestureRecognizer *longpress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(onLongPress:)];
[longpress setMinimumPressDuration:0.5];
[[[CCDirector sharedDirector] view] addGestureRecognizer:longpress];
}
- (void)swipeLeft {
[self move:ccp(-1, 0)];
}
- (void)swipeRight {
[self move:ccp(1, 0)];
}
- (void)swipeDown {
[self move:ccp(0, -1)];
}
- (void)swipeUp {
[self move:ccp(0, 1)];
}
// detect longpress, convert to NodeSpace and check if touch location is within tile boundingbox. If yes, set background white, text black.
- (void)onLongPress:(UILongPressGestureRecognizer *) recognizer {
CGPoint touchPoint = [[CCDirector sharedDirector] convertToGL:[recognizer locationInView:[recognizer view]]];
touchPoint = [self convertToNodeSpace:touchPoint];
if (recognizer.state == UIGestureRecognizerStateBegan) {
for (Tile *tile in self.children) {
if([tile isKindOfClass:[Tile class]]) {
CGRect tileBoundingBox = tile.boundingBox;
if (CGRectContainsPoint(tileBoundingBox, touchPoint)) {
tile.backgroundNode.color = [CCColor whiteColor];
tile.valueLabel.color = [CCColor blackColor];
[self spellWord:tile.value];
[_word setString:[_word lowercaseString]];
CCLOG(#"%#", _word);
}
}
}
}
if (recognizer.state == UIGestureRecognizerStateChanged) {
}
if (recognizer.state == UIGestureRecognizerStateEnded) {
for (Tile *tile in self.children) {
if([tile isKindOfClass:[Tile class]]) {
CGRect tileBoundingBox = tile.boundingBox;
if (CGRectContainsPoint(tileBoundingBox, touchPoint)) {
tile.backgroundNode.color = [tile getColor:tile.value];
tile.valueLabel.color = [self getContrastColor:r green:g blue:b];
}
}
}
}
}
// allow for simultaneous gestures
- (BOOL)gestureRecognizer:(UIGestureRecognizer *) recognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
In answer to your questions:
This doesn't strike me as a coding situation that requires one to subclass UILongPressGestureRecognizer. Having said that, subclassing is often a nice way to clean up one's view controller code so you don't have gory gesture recognizer code in the view controller class. But there's nothing here (as I understand it) that demands that. You generally dive into subclassing of gesture recognizers where you need some special custom behavior (e.g. have the gesture fail if some complicated criterion fails). I'd first see if you could achieve the desired UX with standard gestures before I went down that road, though.
The only reason I could see the swipe gestures interfering with each other is that you've specified that shouldRecognizeSimultaneouslyWithGestureRecognizer should return YES. That's used in cases where you need multiple recognizers running at the same, which doesn't seem necessary here (and only a source of problems).
It's unclear to me as to whether you really wanted a separate swipe gesture or whether you just wanted a single gesture ("long press and drag"). If you needed that separate swipe gesture, though, you would generally specify the relative priority of gesture recognizers by specifying requireGestureRecognizerToFail (e.g. have the swipe require long press to fail in order for the swipe to be recognized). But if you really only have one gesture ("long press and drag"), then only one gesture recognizer is needed.
It seems unnecessary. If you want to detect movement after the long press has been recognized, you can put that "move after long press" code in the if statement for UIGestureRecognizedStateChanged in your onLongPress, which occurs after the long press has been recognized, but before the user lifts their finger. The UILongPressGestureRecognizer is a continuous gesture recognizer which will continue to get updates as the user's finger moves after the gesture was initially recognized.
I know you didn't ask for code, but if you wanted a swipe gesture, as well as a long press gesture that was, essentially, the idea of picking it up and dragging it, you could do something like the following. Note, I make the swipe gesture require the long press to fail, so if the user is long pressing, that takes precedence, otherwise it does swipe. But you may not need the swipe gesture at all, so if you don't need it, just remove it altogether:
#import <UIKit/UIGestureRecognizerSubclass.h>
- (void)viewDidLoad {
[super viewDidLoad];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
[self.view addGestureRecognizer:longPress];
// if you needed a second gesture, a swipe, completely distinct from the long press and drag
// gesture, you could add it like so:
//
// UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
// [swipe requireGestureRecognizerToFail:longPress];
// // do additional swipe configuration
// [self.view addGestureRecognizer:swipe];
}
- (void)handleSwipe:(UISwipeGestureRecognizer *)gesture
{
// do your separate swipe stuff here
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
static UIView *tileToMove;
static CGPoint startCenter;
static CGPoint startLocation;
CGPoint location = [gesture locationInView:self.view];
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
{
// find the tile
tileToMove = [self findTileToMove:location];
if (tileToMove) {
// if found, capture state ...
startCenter = tileToMove.center;
startLocation = location;
// ... and animate "pick up tile", so the user gets positive feedback
// that the drag/swipe portion of the gesture is starting.
[UIView animateWithDuration:0.25 animations:^{
tileToMove.transform = CGAffineTransformMakeScale(1.2, 1.2);
}];
} else {
gesture.state = UIGestureRecognizerStateFailed;
}
break;
}
case UIGestureRecognizerStateChanged:
{
// move the tile as the user's finger moves
CGPoint translate = CGPointMake(location.x - startLocation.x, location.y - startLocation.y);
// note, if you want to constrain the translation to be, for example, on the
// x-axis alone, you could do something like:
//
// CGPoint translate = CGPointMake(location.x - startLocation.x, 0);
tileToMove.center = CGPointMake(startCenter.x + translate.x, startCenter.y + translate.y);
break;
}
case UIGestureRecognizerStateEnded:
{
// animate "drop the tile"
[UIView animateWithDuration:0.25 animations:^{
tileToMove.transform = CGAffineTransformIdentity;
// if you want the tile to "snap" to some location having let it go,
// set the `center` or `frame` here.
}];
// clear our variables, just in case
tileToMove = nil;
startCenter = CGPointZero;
startLocation = CGPointZero;
break;
}
default:
break;
}
}
- (UIView *)findTileToMove:(CGPoint)location
{
for (UIView *tile in self.tiles) {
if (CGRectContainsPoint(tile.frame, location)) {
return tile;
}
}
return nil;
}
This might not be quite the exact UI you're looking for, but it illustrates:
How to have two gestures, where one requires the other to fail in order to establish a precedence between the gestures (and clearly only an issue if you want two distinct gestures, which you probably don't);
To not have shouldRecognizeSimultaneouslyWithGestureRecognizer method because I don't want them both to be recognized simultaneously. Note, that's only needed if you really need two gestures, which you may or may not need; and
How to have a long press that not only recognizes initial long press, but subsequent swipe/drag movement, too.
I have a main view in my program with a draggable view in it. This view can be dragged around with pan gestures. Currently though it uses a lot of code which I want to put in a subclass to reduce complexity. (I eventually want to increase functionality by allowing the user to expand with view with further pan gestures. This means there will be lots more code clogging up my view controller if I can't sort this out first)
Is it possible to have the code for a gesture recogniser in the subclass of a class and still interact with views in the parent class.
This is the current code I am using to enable pan gesture in the parent class:
-(void)viewDidLoad {
...
UIView * draggableView = [[UIView alloc]initWithFrame:CGRectMake(highlightedSectionXCoordinateStart, highlightedSectionYCoordinateStart, highlightedSectionWidth, highlightedSectionHeight)];
draggableView.backgroundColor = [UIColor colorWithRed:121.0/255.0 green:227.0/255.0 blue:16.0/255.0 alpha:0.5];
draggableView.userInteractionEnabled = YES;
[graphView addSubview:draggableView];
UIPanGestureRecognizer * panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panWasRecognized:)];
[draggableView addGestureRecognizer:panner];
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
UIView * draggedView = panner.view;
CGPoint offset = [panner translationInView:draggedView.superview];
CGPoint center = draggedView.center;
// We want to make it so the square won't go past the axis on the left
// If the centre plus the offset
CGFloat xValue = center.x + offset.x;
draggedView.center = CGPointMake(xValue, center.y);
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:draggedView.superview];
}
(Thanks to Rob Mayoff for getting me this far)
I have now added a subclass of the view but can't figure out how or where I need to create the gesture recogniser as the view is now being created in the subclass and added to the parent class.
I really want the target for the gesture recogniser to be in this subclass but when I try to code it nothing happens.
I have tried putting all the code in the subclass and adding the pan gesture to the view but then I get a bad access crash when I try to drag it.
I am currently trying to use
[graphView addSubview:[[BDraggableView alloc] getDraggableView]];
To add it to the subview and then setting up the view (adding the pan gesture etc) in the function getDraggableView in the subclass
There must be a more straight forward way of doing this that I haven't conceptualised yet - I am still pretty new dealing with subclasses and so am still learning how they all fit together.
Thanks for any help you can give
I think I might of figured this one out.
In the parent class I created the child class variable:
BDraggableView * draggableViewSubClass;
draggableViewSubClass = [[BDraggableView alloc] initWithView:graphView andRangeChart: [rangeSelector getRangeChart]];
This allowed me to initialise the child class with the view I wanted to have the draggable view on: graphView
Then in the child view I set up the pan gesture as I normally would but added it to this view carried through:
- (id)initWithView:(UIView *) view andRangeChart: (ShinobiChart *)chart {
self = [super initWithNibName:nil bundle:nil];
if (self) {
// Custom initialization
parentView = view;
[self setUpViewsAndPans];
}
return self;
}
- (void)setUpViewsAndPans {
draggableView = [[UIView alloc]initWithFrame:CGRectMake(highlightedSectionXCoordinateStart, highlightedSectionYCoordinateStart, highlightedSectionWidth, highlightedSectionHeight)];
draggableView.backgroundColor = [UIColor colorWithRed:121.0/255.0 green:227.0/255.0 blue:16.0/255.0 alpha:0.5];
draggableView.userInteractionEnabled = YES;
// Add the newly made draggable view to our parent view
[parentView addSubview:draggableView];
[parentView bringSubviewToFront:draggableView];
// Add the pan gesture
UIPanGestureRecognizer * panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panWasRecognized:)];
[draggableView addGestureRecognizer:panner];
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
UIView * draggedView = panner.view;
CGPoint offset = [panner translationInView:draggedView.superview];
CGPoint center = draggedView.center;
CGFloat xValue = center.x + offset.x;
draggedView.center = CGPointMake(xValue, center.y);
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:draggedView.superview];
}
It took me a while to straighten it in my head that we want to do all the setting up in our subclass and then add this view with its characteristics to the parent view.
Thanks for all the answers provided they got me thinking along the right lines to solve it
I think you want to subclass UIView and make your own DraggableView class. Here, you can add swipe and pan gesture recognizers. This would be in the implementation of a subclass of UIView
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
UIGestureRecognizer *gestRec = [[UIGestureRecognizer alloc] initWithTarget:self
action:#selector(detectMyMotion:)];
[self addGestureRecognizer:gestRec];
}
return self;
}
- (void)detectMyMotion:(UIGestureRecognizer *)gestRect
{
NSLog(#"Gesture Recognized");
// maybe even, if you wanted to alert your VC of a gesture...
[self.delegate alertOfGesture:gestRect];
// your VC would be alerted by delegation of this action.
}
I am trying to rotate the custom view added to the main view. I have tried to add the gesture recognizer to both main view and sub view. Here is my code
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *demoView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
UIRotationGestureRecognizer *rg= [[UIRotationGestureRecognizer alloc]initWithTarget:self action:#selector(handleTap:)];
[rg setDelegate:self];
self.view.userInteractionEnabled = YES;
demoView.userInteractionEnabled = YES;
[demoView addGestureRecognizer:rg];
[self.view addGestureRecognizer:rg];
demoView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:demoView];
}
-(IBAction)handleTap:(UIRotationGestureRecognizer *)recognizer
{
//NSLog(#"HERE");
recognizer.view.transform = CGAffineTransformMakeRotation([recognizer rotation]);
recognizer.rotation = 0;
//NSLog(#"AND HERE");
}
the problem is that one geturerecognizer can only ever be attached to one view. for your requirements, you need two gesturerecognizers, but you can make them call the same selector, so theres no need to implement it two times.
EDIT:
Its kinda hard to find this stated clearly in the docs, but considering that the view- property is a single view, and not an array or set or whatever, it makes alot of sense.
Heres the docs:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/instp/UIGestureRecognizer/view
Next EDIT
This stackoverflow-answer also illustrates my point:
Can you attach a UIGestureRecognizer to multiple views?
so whoever downvoted my answer, at least tell me why.