In my app I have this code to drag an object
I set my gesture:
UILongPressGestureRecognizer *downwardGesture = [[UILongPressGestureRecognizer new] initWithTarget:self action:#selector(dragGestureChanged:)];
downwardGesture.minimumPressDuration = 0.2;
[grid_element addGestureRecognizer:downwardGesture];
for (UILongPressGestureRecognizer *gestureRecognizer in self.view.gestureRecognizers)
{
[gestureRecognizer requireGestureRecognizerToFail:downwardGesture];
}
and in my method:
- (void) dragGestureChanged:(UILongPressGestureRecognizer*)gesture{
UIImageView *imageToMove;
CGPoint pointInSelfView;
if (gesture.state == UIGestureRecognizerStateBegan)
{
dragging = TRUE;
CGPoint location = [gesture locationInView:grid_element];
NSIndexPath *selectedIndexPath = [grid_element indexPathForItemAtPoint:location];
if (selectedIndexPath==nil) {
dragging = FALSE;
return;
}
indexToHide = selectedIndexPath.row;
imageToMove = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"image_1.png"]];
[self.view addSubview:imageToMove];
pointInSelfView = [gesture locationInView:self.view];
[imageToMove setCenter:pointInSelfView];
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
pointInSelfView = [gesture locationInView:self.view];
[imageToMove setCenter:pointInSelfView];
}
else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateCancelled ||
gesture.state == UIGestureRecognizerStateFailed)
{
dragging = FALSE;
}
}
it work in UIGestureRecognizerStateBegan, then imageToMove is added in self.view in the correct position but imageToMove don't drag in UIGestureRecognizerStateChanged; I show a NSLog for pointInSelfView in UIGestureRecognizerStateChanged and it changes correctly; where is the problem?
EDIT
if I use imageToMove as IBOutlet it work fine, but I don't understand the difference.
If you are using ARC, the iOS system is releasing object-imageToMove.
Creating a property with retain attribute, makes system retains the object and hence it moves.
The only thing I am confused is why there is no error in [imageToMove setCenter:pointInSelfView];. There should be an error saying - message is sent to deallocated instance. But I am pretty sure that the problem is due to memory release of imageToMove.
Related
I am having trouble using initWithItem:attachedToItem: Initializes an attachment behavior that connects the center point of a dynamic item to the center point of another dynamic item.
But when I changed the center point of topview using method pan,only the topview moved around,I can't get the other view to move.Isn't it should be moving together?
(BTW I am trying to implement a pile of cards and move around all together when I pan the card in the top.)
-(void)pinch:(UIPinchGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateChanged){
CGPoint pinchCen = [gesture locationInView:self.cardArea];
if (gesture.scale <= 0.5 && !self.pileStat) {
self.pileStat = !self.pileStat;
NSUInteger number = [self.cardViews count];
UIView *topView = [self.cardViews lastObject];
[topView addGestureRecognizer:[[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(pan:)]];
for (int i = 0; i < number;i++) {
UIView *cardView = self.cardViews[i];
[UIView animateWithDuration:0.5 animations:^{cardView.center = CGPointMake(pinchCen.x+i%10*0.5, pinchCen.y+i%10*0.5);} completion:^(BOOL finished){
if(i != number - 1){
UIAttachmentBehavior *attach = [[UIAttachmentBehavior alloc]initWithItem:cardView attachedToItem:topView];
[self.animator addBehavior:attach];
}
}];
}
}
else if(gesture.scale > 1.5 && self.pileStat)
{
self.pileStat = !self.pileStat;
}
}else if (gesture.state == UIGestureRecognizerStateEnded){
gesture.scale = 1.0f;
}
}
-(void)pan:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateChanged || UIGestureRecognizerStateEnded) {
UIView *topView = [self.cardViews lastObject];
CGPoint trans = [gesture translationInView:self.cardArea];
topView.center = CGPointMake(trans.x+topView.center.x, trans.y+topView.center.y);
[gesture setTranslation:CGPointMake(0, 0) inView:self.cardArea];
}
}
setCenter does not play nice with UIDynamicAnimator. Instead of changing the center coordinate of your top view, you should use another UIAttachmentBehavior that you attach to your touch. Replace your pan gesture handler with this method:
//Add a variable in your interface or header section called "touchAttachment" of type UIAttachmentBehavior
- (void) pan: (UIPanGestureRecognizer *) sender{
UIView* topCard = [self.cardViews lastObject];
if (sender.state == UIGestureRecognizerStateBegan){
_touchAttachment = [[UIAttachmentBehavior alloc] initWithItem:topCard
attachedToAnchor: [sender locationInView:self.view]];
[self.animator addBehavior:_touchAttachment];
}
else if (sender.state == UIGestureRecognizerStateChanged){
[_touchAttachment setAnchorPoint: [sender locationInView: self.view]];
}
else if (sender.state == UIGestureRecognizerStateEnded){
[self.animator removeBehavior: _touchAttachment];
_touchAttachment = nil;
}
}
Make sure you add the "touchAttachment" variable.
Hope it helps :)
I am adding UIPanGestureRecogniser to drag and drop images in a view.I want to drag only one image at a time.but I am able to drag two images at a time.which should not happen.
I am stuck with this bug since morning.I tried all the ways i found on google.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setMultipleTouchEnabled:NO];
for(UIImageView *iView in self.movableArray){
if ([iView isMemberOfClass:[UIImageView class]]){
UIPanGestureRecognizer * recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[iView addGestureRecognizer:recognizer];
[iView setUserInteractionEnabled:YES];
recognizer.delegate = self;
}
}
}
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
[gameView bringSubviewToFront:[(UIPanGestureRecognizer *)recognizer view]];
CGPoint translation = [recognizer translationInView:gameView];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:gameView];
self.dragObjectImageView = (UIImageView*)recognizer.view;
if(recognizer.state == UIGestureRecognizerStateBegan){
int ind = [self.movableArray indexOfObject:self.dragObjectImageView];
for(int i = 0 ; i < [self.movableArray count] ; i ++){
if( i != ind ){
[[self.movableArray objectAtIndex:i] removeGestureRecognizer:recognizer];
}
}
self.homePosition = self.dragObjectImageView.frame;
}
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint touchPoint = [recognizer locationInView:gameView];
for (UIImageView *iView in self.staticArray) {
if ([iView isMemberOfClass:[UIImageView class]]) {
if (touchPoint.x > iView.frame.origin.x &&
touchPoint.x < iView.frame.origin.x + iView.frame.size.width &&
touchPoint.y > iView.frame.origin.y &&
touchPoint.y < iView.frame.origin.y + iView.frame.size.height)
{
self.dropTargetImageView = iView;
}
}
}
if(self.dragObjectImageView.tag == self.dropTargetImageView.tag){
self.dragObjectImageView.frame = CGRectMake(self.dropTargetImageView.frame.origin.x, self.dropTargetImageView.frame.origin.y + self.dropTargetImageView.frame.size.height/2 - 15, self.dragObjectImageView.frame.size.width, self.dragObjectImageView.frame.size.height);
[self.dragObjectImageView removeGestureRecognizer:recognizer];
}
}else{
self.dragObjectImageView.frame = self.homePosition;
}
}
}
This happens because you are adding one UIPanGestureRecognizer for each of your imageViews. Try adding only one to your self.view (and have your imageViews to setUserInteractionEnabled:NO, otherwise they will trap the touch). Also put
recognizer.maximumNumberOfTouches = 1;
Before you add it to your view. All you have to do now is to test which image view should be dragged in your handlePan method. You should check the recognizer state and when it turns to UIGestureRecognizerStateBegan you should save which imageView is being dragged. Then as the state is UIGestureRecognizerStateChanged just drag the view around. The most general way you can find out which view was touched (as i don't know your full view hierarchy) would be to do something like:
NSUInteger index = [self.movableArray indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop)){
UIView* hitTest = (UIView*)obj;
return [hitTest pointInside:firstTouch withEvent:nil];
}];
if ( index != NSNotFound )
self.draggingView = self.moveableArray[index];
else
self.draggingView = nil;
Then of course if self.draggingView is nil you would do nothing when the user is panning around.
You should create a mechanism to setUserInteractionEnable = NO when your
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer;
Is called on the other images. You can either do that, or disable the others UIPanGestureRecognizer, like: myPanGestureRecognizer.enabled = NO;
A quick example:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer
{
UIImageView *currentDraggedImageView = recognizer.view;
// Based on this, you can iterate again on your UIImageViews and disable them.
// Once your work is done with gesture recogniser, you can re-enable them.
}
I want to move two images with same UIPanGestureRecognizer ,
I am able to move the first image, but as soon as I try to move the second image the first one goes back to its original position. I want the first image to retain it's after changed position.
-(void) viewWillAppear:(BOOL)animated
{
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanSuper:)];
[self.view addGestureRecognizer:pan];
}
- (void)handlePanSuper:(UIPanGestureRecognizer *)sender
{
static UIImageView *viewToMove;
static CGPoint originalCenter;
if (sender.state == UIGestureRecognizerStateBegan)
{
CGPoint location = [sender locationInView:self.view];
if (CGRectContainsPoint(self.imageView.frame, location))
{
viewToMove = imageView;
originalCenter = viewToMove.center;
}
else if (CGRectContainsPoint(self.image2.frame, location))
{
viewToMove = image2;
originalCenter = viewToMove.center;
}
else
{
viewToMove = nil;
}
if (viewToMove)
{
viewToMove.alpha = 0.8;
[viewToMove.superview bringSubviewToFront:viewToMove];
NSLog(#"hi i am being touched.");
}
}
if (sender.state == UIGestureRecognizerStateChanged && viewToMove != nil)
{
CGPoint translation = [sender translationInView:self.view];
viewToMove.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y);
}
else if ((sender.state == UIGestureRecognizerStateEnded ||
sender.state == UIGestureRecognizerStateFailed ||
sender.state == UIGestureRecognizerStateCancelled) && viewToMove != nil)
{
// do whatever post dragging you want, e.g.
// snap the piece into place
// CGPoint center = viewToMove.center;
//viewToMove.center = center;
viewToMove.alpha = 1.0;
viewToMove = nil;
}
}
You are setting your gesture recognizer in your superView. Why? You could set it inside the views that you are trying to move, it's easier and less likely to strange behaviors I edited your code, see below (not tested)
-(void) viewWillAppear:(BOOL)animated
{
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanSuper:)];
self.imageView.userInteractionEnabled = self.image2.userInteractionEnabled = YES;
[self.imageView addGestureRecognizer:pan];
[self.image2 addGestureRecognizer:pan];
}
- (void)handlePanSuper:(UIPanGestureRecognizer *)sender
{
UIImageView *viewToMove = (UIImageView*)sender.view;
CGPoint originalCenter = viewToMove.center;
if (sender.state == UIGestureRecognizerStateBegan)
{
CGPoint location = [sender locationInView:self.view];
viewToMove.alpha = 0.8;
[viewToMove.superview bringSubviewToFront:viewToMove];
NSLog(#"hi i am being touched.");
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [sender translationInView:self.view];
viewToMove.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y);
}
else if ((sender.state == UIGestureRecognizerStateEnded ||
sender.state == UIGestureRecognizerStateFailed ||
sender.state == UIGestureRecognizerStateCancelled))
{
// do whatever post dragging you want, e.g.
// snap the piece into place
// CGPoint center = viewToMove.center;
//viewToMove.center = center;
viewToMove.alpha = 1.0;
viewToMove = nil;
}
}
Please try this, and let me know if worked.
I'm working on a D&D in a UISplitViewController (based on Xcode template project), from the MasterViewController to the DetailViewController.
Basically, what I'm doing is creating a UILongPressGestureRecognizer and placing it on the self.tableview property of the MasterViewController.
Below is my gesture recognizer.
- (void)gestureHandler:(UIGestureRecognizer*)gesture
{
CGPoint location;
NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:[gesture locationInView:self.tableView]];
if (gesture.state == UIGestureRecognizerStateBegan) {
// Create draggable view
NSString* imageCanvasName = [self imageNameByIndexPath:indexPath isDetail:YES];
UIImage* imageCanvas = [UIImage imageNamed:imageCanvasName];
_draggedView = [[UIImageView alloc] initWithImage:imageCanvas];
// Create drag-feedback window, add the drag-view and make the drag-window visible
CGRect windowFrame = self.view.window.frame;
_dragFeedbackWindow = [[UIWindow alloc] initWithFrame:windowFrame];
location = [gesture locationInView:gesture.view.window];
[_draggedView setCenter:location];
[_dragFeedbackWindow addSubview:_draggedView];
[_dragFeedbackWindow setHidden:NO];
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
// Update drag-view location
location = [gesture locationInView:gesture.view.window];
[_draggedView setCenter:location];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// Disconnect drag-view and hide drag-feedback window
[_draggedView removeFromSuperview];
[_dragFeedbackWindow setHidden:YES];
// If drop is in a valid location...
if ([self.tableView pointInside:_draggedView.center withEvent:nil] == NO)
{
// Get final location in detailViewController coordinates
location = [gesture locationInView:self.detailViewController.view];
[_draggedView setCenter:location];
[self.detailViewController.view addSubview:_draggedView];
}
}
else {
NSLog(#"%s unrecognized gesture %d", __FUNCTION__, gesture.state);
}
}
All works very nicely when the iPad is in portrait mode - the drag, the drop - the works.
My problem starts if the iPad is rotated...
In such a case, my _draggedView appears "counter-rotated" - it will "reflect" the iPad's rotation - until dropped.
It's like I must apply some rotation to _dragFeedbackWindow - but I tried a number of things, failing...
Any idea?
Thanks!
OK - I figured out the "WHY" this happens, and the "HOW" to fix (and will attach the handler code to do this correctly, all below.
Here's the code... "Just" add it to your MAsterViewController (if - like me - you want to drag from the master to the detail...)
// A simple UIView extension to rotate it to a given orientation
#interface UIView(oriented)
- (void)rotateToOrientation:(UIInterfaceOrientation)orientation;
#end
#implementation UIView(oriented)
- (void)rotateToOrientation:(UIInterfaceOrientation)orientation {
CGFloat angle = 0.0;
switch (orientation) {
case UIInterfaceOrientationPortraitUpsideDown:
angle = M_PI;
break;
case UIInterfaceOrientationLandscapeLeft:
angle = - M_PI / 2.0f;
break;
case UIInterfaceOrientationLandscapeRight:
angle = M_PI / 2.0f;
break;
default: // as UIInterfaceOrientationPortrait
angle = 0.0;
break;
}
self.transform = CGAffineTransformMakeRotation(angle);
}
Now, the LongPress gesture handler...
- (void)gestureHandler:(UIGestureRecognizer*)gesture
{
CGPoint location;
UIWindow* dragFeedback = [UIApplication sharedApplication].delegate.window;
if (gesture.state == UIGestureRecognizerStateBegan) {
// Create draggable view
_draggedView = [[UIImageView alloc] initWithImage:#"someImage.png"];
// Required to adapt orientation... WORKS, BUT WHY NEEDED???
[_draggedView rotateToOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
// Create drag-feedback window, add the drag-view and make the drag-window visible
location = [gesture locationInView:dragFeedback];
[_draggedView setCenter:location];
[dragFeedback addSubview:_draggedView];
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
// Update drag-view location
location = [gesture locationInView:dragFeedback];
[_draggedView setCenter:location];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// Disconnect drag-view and hide drag-feedback window
[_draggedView removeFromSuperview];
// Get final location in detailViewController coordinates
location = [gesture locationInView:self.detailViewController.view];
[_draggedView setCenter:location];
// "Noramlize" orientation... WORKS, BUT WHY NEEDED???
[_draggedView rotateToOrientation:UIInterfaceOrientationPortrait];
[self.detailViewController.view addSubview:_draggedView];
}
else {
NSLog(#"%s unrecognized gesture %d", __FUNCTION__, gesture.state);
}
}
This works like a charm - let me know how it works for you (if you need a sample project, let me know...)
Thanks for your solution! You got me 90% of the way there. In payment, I will give you the last 10% :)
The issue seems to be that the UIWindow never gets rotated, only its subviews do! So the solution is to use a subview.
I've also added some code that shows how to see which cell is selected (assuming your master view is a UITableViewController).
- (IBAction)handleGesture:(UIGestureRecognizer *)gesture
{
CGPoint location;
UIWindow *window = [UIApplication sharedApplication].delegate.window;
//The window doesn't seem to rotate, so we'll get the subview, which does!
UIView *dragFeedback = [window.subviews objectAtIndex:0];
if (gesture.state == UIGestureRecognizerStateBegan) {
location = [gesture locationInView:dragFeedback];
//which cell are we performing the long hold over?
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:[gesture locationInView:self.tableView]];
UITableViewCell *selecteCell = [self.tableView cellForRowAtIndexPath:indexPath];
NSLog(#"Cell %#", selecteCell);
// Create draggable view
_draggedView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"someImage.png"]];
// Create drag-feedback window, add the drag-view and make the drag-window visible
[_draggedView setCenter:location];
[dragFeedback addSubview:_draggedView];
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
// Update drag-view location
location = [gesture locationInView:dragFeedback];
[_draggedView setCenter:location];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// Disconnect drag-view and hide drag-feedback window
[_draggedView removeFromSuperview];
// Get final location in detailViewController coordinates
location = [gesture locationInView:self.detailViewController.view];
[_draggedView setCenter:location];
[self.detailViewController.view addSubview:_draggedView];
}
else {
NSLog(#"%s unrecognized gesture %d", __FUNCTION__, gesture.state);
}
}
In the application I am creating, I have a UIView named AdvancedView1. The user can drag the view around the screen. The user needs to drag the view to a certain position on the screen. If the view is dropped into a certain area, then the view would "snap" into a defined location. When trying to use IF AND statements, I am getting an error. Any suggestions?
Also "origin" is a CGPoint declared in the .h file.
- (void)pan:(UIPanGestureRecognizer *) gesture
{
if (gesture.state == UIGestureRecognizerStateChanged)
{
CGPoint origin = [gesture locationInView:[self superview]];
[self bringSubviewToFront:[self superview]];
[self setCenter:origin];
}
if (gesture.state == UIGestureRecognizerStateEnded)
{
if (origin.x >= 574.0 && origin.x <= 724.0 ) {
self.frame = CGRectMake(574.0, 184.0, self.frame.size.width, self.frame.size.height);
}
}
}
I am getting the following error for "origin.x >= ...."
"Request for member "x" in something not a structure or union"
origin variable scope is limited to the 1st if block and it is not visible in the 2nd one. Move its declaration to the function scope:
- (void)pan:(UIPanGestureRecognizer *) gesture
{
CGPoint origin = [gesture locationInView:[self superview]];
if (gesture.state == UIGestureRecognizerStateChanged)
{
[self bringSubviewToFront:[self superview]];
[self setCenter:origin];
}
if (gesture.state == UIGestureRecognizerStateEnded)
{
if (origin.x >= 574.0 && origin.x <= 724.0 ) {
self.frame = CGRectMake(574.0, 184.0, self.frame.size.width, self.frame.size.height);
}
}
}