Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS >> Dragged View is Jumping Back to Original Position >> Auto Layout Combined with UIPanGestureRecognizer Issue

I have several UIViews that the user should be able to drag & drop. I use UIPanGestureRecognizer to manage the d&d action (see code below). The d&d action is working fine, but when I start dragging another UIView, the one that I just dropped is jumping back to its original position.

- (void)handlePan:(UIPanGestureRecognizer *)recognizer {

    CGPoint translation = [recognizer translationInView:self.view];

    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
            for (UIView* checkView in dragAndDropImageViewsArray) {
                if (checkView == recognizer.view) {
                    [checkView.superview bringSubviewToFront:checkView]; //this is done to make sure the currently dragged object is the top one
                }
            }
            break;

        case UIGestureRecognizerStateChanged:
            recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y);
            break;

        case UIGestureRecognizerStateEnded:
            NSLog(@"UIGestureRecognizerStateEnded");
            break;

        default:
            break;
    }

    [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];

}

I tried several things that "sloved" the problem but caused other problems:

  • Cancel Auto-Layout: That totally solves it but now I have to start calculate and manage object locations and sizes for different devices and orientations.
  • Use layer.zPosition instead of bringSubviewToFront: that makes objects appear above other objects while apparently avoiding the "jump back", but does not allow me to change the hierarchy between the different draggable objects.

I started deepening my understanding about Auto Layout, but it gets very complicated when it seems to me that what I'm looking for should be very simple; I saw many methods that deal with performing a "re-auto-layouting", but I'm not clear about how to work with them.

Is there a simple solution here? like calling a method that overrides the IB Auto-Layout constrains and redefine them according to the UIView new position?

Any assistance would be very musch appreciated.

like image 465
Ohad Regev Avatar asked Apr 21 '13 10:04

Ohad Regev


2 Answers

I think that if you are using autolayout and you want to change the position of a view, you need to be updating the constraints on the view instead of manually setting the center of a view. From the description it sounds like you are setting the center, but when the app next lays out the views (eg you drag another) it re-sets the center to whatever autolayout says it should be.

I'm not sure what the rest of your app looks like, but you may be able to find nearby views and add constraints to it, or you might prefer to just update the constraints of the dragged view and set the leading space and top space to the superview manually. If you've got a collection handy you could also clear all the constraints and then re-add them as you'd like to keep padding in between the views.

To set the constraints in code you can clear the constraints for the moved view and re-add them, or if you have a view in IB you can drag the constraints into code to create an outlet. Look at the removeConstraints and addConstraints methods to do it in code.

Lastly, if it works how you'd like without autolayout, you could try setting translatesAutoresizingMaskIntoConstraints = YES on the moved views, but be aware that may cause conflicts elsewhere.

like image 152
James Avatar answered Nov 08 '22 12:11

James


This is all possible using Interface Builder. Here is the code I used to do this:

@property (weak,nonatomic) IBOutlet NSLayoutConstraint *buttonXConstraint;
@property (weak,nonatomic) IBOutlet NSLayoutConstraint *buttonYConstraint;

Then hook up those IBOutlets to the Horizontal Constraint(X Position) and Vertical Constraint(Y Position) in the Interface Builder. Make sure that the Constraints are hooked up to the base view and not it's closest view.

Hook up a pan gesture and then use this following code to drag your object around:

- (IBAction)panPlayButton:(UIPanGestureRecognizer *)sender
{
    if(sender.state == UIGestureRecognizerStateBegan){

    } else if(sender.state == UIGestureRecognizerStateChanged){
        CGPoint translation = [sender translationInView:self.view];

        //Update the constraint's constant
        self.buttonXConstraint.constant += translation.x;
        self.buttonYConstraint.constant += translation.y;

        // Assign the frame's position only for checking it's fully on the screen
        CGRect recognizerFrame = sender.view.frame;
        recognizerFrame.origin.x = self.buttonXConstraint.constant;
        recognizerFrame.origin.y = self.buttonYConstraint.constant;

        // Check if UIImageView is completely inside its superView
        if(!CGRectContainsRect(self.view.bounds, recognizerFrame)) {
            if (self.buttonYConstraint.constant < CGRectGetMinY(self.view.bounds)) {
                self.buttonYConstraint.constant = 0;
            } else if (self.buttonYConstraint.constant + CGRectGetHeight(recognizerFrame) > CGRectGetHeight(self.view.bounds)) {
                self.buttonYConstraint.constant = CGRectGetHeight(self.view.bounds) - CGRectGetHeight(recognizerFrame);
            }

            if (self.buttonXConstraint.constant < CGRectGetMinX(self.view.bounds)) {
                self.buttonXConstraint.constant = 0;
            } else if (self.buttonXConstraint.constant + CGRectGetWidth(recognizerFrame) > CGRectGetWidth(self.view.bounds)) {
                self.buttonXConstraint.constant = CGRectGetWidth(self.view.bounds) - CGRectGetWidth(recognizerFrame);
            }
        }

        //Layout the View
        [self.view layoutIfNeeded];
    } else if(sender.state == UIGestureRecognizerStateEnded){

    }

    [sender setTranslation:CGPointMake(0, 0) inView:self.view];
}

I have added code in there to check the frame and make sure that it's not going outside the view. If you want to allow the object to be partially off the screen, feel free to take it out.

This took a little bit of time for me to realize that it was the use of AutoLayout that was resetting the view but constraints will do what you need here!

like image 27
jkanter Avatar answered Nov 08 '22 11:11

jkanter