0

I'm facing a very strange problem - at least to me - and it seems like I'm going crazy :) I've got a UIView to which I add a UIPanGestureRecognizer in order to be able to move it to some extent vertically on the screen; here's the code where I do it:

- (id)init {

    // ...
    UIPanGestureRecognizer *gestureRecognizerPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesturePan:)];
    [self.viewMenuProper addGestureRecognizer:gestureRecognizerPan];

    imageArrowOriginalFrame = self.imageArrow.frame;
    // ...
}

Said UIView, self.viewMenuProper, is supposed to be draggable towards the bottom of the screen (and back up towards it default position). Just above it on the screen is placed a UIImageView whose top edge's position and width are meant to maintain their values but its height is supposed to be changed depending on the position of the self.viewMenuProper so that it seems like it connects that UIView with some point at the top of the screen even when the UIView is moved around.

So here's the method which handles the gestures:

- (void)handleGesturePan:(UIPanGestureRecognizer *)gestureRecognizerPan {

    if (gestureRecognizerPan.state == UIGestureRecognizerStateBegan) {

        gesturePanPreviousY = [gestureRecognizerPan locationInView:self].y;
    }
    else if (gestureRecognizerPan.state == UIGestureRecognizerStateChanged) {

        float gesturePanCurrentY = [gestureRecognizerPan locationInView:self].y;
        float gesturePanDeltaY = gesturePanCurrentY - gesturePanPreviousY;

        float imageArrowFrameNewHeight = self.imageArrow.frame.size.height + gesturePanDeltaY;

        if (imageArrowFrameNewHeight <= 200 && imageArrowFrameNewHeight >= imageArrowOriginalFrame.size.height) {

            [self.imageArrow setFrame:CGRectMake(imageArrowOriginalFrame.origin.x, imageArrowOriginalFrame.origin.y, imageArrowOriginalFrame.size.width, imageArrowFrameNewHeight)]; // line A
        }

        // Check if the transformed View won't be too far up any way of the screen
        float gesturePanNewY = self.viewMenuProper.transform.ty + gesturePanDeltaY;

        if (gesturePanNewY <= 200 && gesturePanNewY > 0) {

            [self.viewMenuProper setTransform:CGAffineTransformTranslate(self.viewMenuProper.transform, 0, gesturePanDeltaY)]; // line B
        }

        gesturePanPreviousY = gesturePanCurrentY;
    }
    // The below animates the Views back to their default positions
    else if (gestureRecognizerPan.state == UIGestureRecognizerStateEnded || gestureRecognizerPan.state == UIGestureRecognizerStateFailed || gestureRecognizerPan.state == UIGestureRecognizerStateCancelled) {

        [UIView animateWithDuration:1.0 animations:^{

            [self.viewMenuProper setTransform:CGAffineTransformIdentity];
            // NOTE: the below line prevents the animation from jumping wrong way 1st before animating the way it should
            // http://stackoverflow.com/questions/12535647/uiview-animation-jumps-at-beginning
            [self layoutIfNeeded];
        }];

        self.imageArrow.frame = imageArrowOriginalFrame;
    }
}

Now this doesn't work: what happens is viewMenuProper moves nicely but imageArrow only stretches when the former one is at its furthermost position and not moving anymore. If I comment out line A (take a look at the code above), then line B works fine; similarly, if I comment out line B, then line A works fine. But when they're both uncommented, it's just like I wrote: imageArrow doesn't stretch like expected.

Any ideas? If you need any more clarification let me know.

Edit: both the UIView and the UIImageView are children of the same parent UIView - they're not "connected" to one another in the .xib file where I created their layout.

JakeP
  • 1,736
  • 4
  • 23
  • 31
  • Are you using AutoLayout? – brandonscript Jun 20 '14 at 16:02
  • @remus - I think so but I can't remember how I can confirm that, could you give me any directions? – JakeP Jun 20 '14 at 16:05
  • Open your main storyboard; from the right-hand pane (utilities) there's an icon that looks like a document (paper). That's the file inspector -- select that and you'll see "Use AutoLayout". If you are, it's most certainly the culprit. You can either disable it, or dive into it and create appropriate constraints (not a simple undertaking!) – brandonscript Jun 20 '14 at 16:08
  • Ah, ok, found it just before reading your reply :) Yes, I AM using AutoLayout. I'll try to disable it and see if that helps. – JakeP Jun 20 '14 at 16:10
  • Hah, that WAS the culprit! I wasted 2-3h today trying to fix this little thing... Anyway, if you write that in an answer to my question rather than just a comment, I'll happily accept it :) I could also use some clarification as to why it behaves like that... – JakeP Jun 20 '14 at 16:17

1 Answers1

1

In your case, as we determined, AutoLayout was the culprit here.

To disable it, uncheck this box:

enter image description here

The reason AutoLayout is breaking your frame changes is largely due to it's use of constraints. When you create a control out of the box, iOS automatically translates it's current frame into enough constraints to satisfy an un-ambiguous positioning of the element.

Imagine you have a view that you've positioned top/left. If you added a constraint that said it always HAD to be 50pt by 50pt, AND touch the top left and bottom right corners, you'd end up with a breaking constraint.

Conversely, imagine you don't give an element any constraints at all: iOS has to guess where you want it to be. In your case, you're translating the frame of the control, but with the "assumed" constraints in place, they're telling the control it's in the wrong place, and thus tries to move it back.

The methods that call and update autolayout are:

[myView setNeedsLayout]; // Tells your view that it needs to be laid out again
[self.view layoutIfNeeded]; // Tells the view to update its constraints if they've changed

Luckily in your case you can just disable AutoLayout and go about your day! Unfortunately if you decide to actually use AutoLayout (creating constraints and auto-sizing elements) you've got a much more complicated task if you want to manipulate frames and such. Often times you may find creating IBOutlets for constraints can be helpful - in this way you can programmatically update the constraints.

brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • Thanks! I'll have to do some background reading on AutoLayout and then come back to this I think. Sometimes it seems that creating a UI in Android is so much easier! – JakeP Jun 20 '14 at 16:31
  • @JakeP AutoLayout is ridiculously more complicated than it should be unfortunately. Have a look at something like [Masonry](https://github.com/Masonry/Masonry) to make using it **a lot** easier. – brandonscript Jun 20 '14 at 17:04