5

So i am trying to animate some layout constraints and am having miserable luck, i just dont see anything and then i get an error message saying how it can't simultaneously satisfy all constrains etc.

I am using a class called PullableView whose animation is using old style [UIView commitAnimation] so i subclassed it and added in my code for what i believe would be for animating constraints...No such luck and attempting to animate it or even get it to do much of anything is proving to be difficult i just get "Unable to simultaneously satisfy constraints".. Problem is i am fairly new to this constraint business so i would not know where to start.

here is the error the other one is pretty much the same but for centerY.

"<NSLayoutConstraint:0x7c8a3a0 StyledPullableView:0x905a9b0.centerX == UIView:0x9054680.centerX>", "<NSLayoutConstraint:0x7c6b480 StyledPullableView:0x905a9b0.centerX == UIView:0x9054680.centerX + 128>"

I of course do [pullRightView setTranslatesAutoresizingMaskIntoConstraints:NO]; before calling this

any help appreciated.

- (void)setOpenedForConstraints:(BOOL)op animated:(BOOL)anim
{
    opened = op;

    if (anim)
    {        
        NSLayoutConstraint *constraintX = [NSLayoutConstraint constraintWithItem:self
                                                                               attribute:NSLayoutAttributeCenterX
                                                                               relatedBy:NSLayoutRelationEqual
                                                                                  toItem:_parentView
                                                                               attribute:NSLayoutAttributeCenterX
                                                                              multiplier:1
                                                                                constant:0];

        NSLayoutConstraint *constraintY = [NSLayoutConstraint constraintWithItem:self
                                                                               attribute:NSLayoutAttributeCenterY
                                                                               relatedBy:NSLayoutRelationEqual
                                                                                  toItem:_parentView
                                                                               attribute:NSLayoutAttributeCenterY
                                                                              multiplier:1
                                                                                constant:0];

        [_parentView addConstraint:constraintX];
        [_parentView addConstraint:constraintY];

        constraintX.constant = opened ? self.openedCenter.x : self.closedCenter.x;
        constraintY.constant = opened ? self.openedCenter.y : self.closedCenter.y;
    }

    if (anim)
    {

        // For the duration of the animation, no further interaction with the view is permitted
        dragRecognizer.enabled = NO;
        tapRecognizer.enabled = NO;

        //[UIView commitAnimations];

        [UIView animateWithDuration:animationDuration
                         animations:^
         {
             [self layoutIfNeeded];
         }];

    }
    else
    {

        if ([delegate respondsToSelector:@selector(pullableView:didChangeState:)])
        {
            [delegate pullableView:self didChangeState:opened];
        }
    }
}
Genhain
  • 1,937
  • 1
  • 19
  • 38

1 Answers1

11

A couple of thoughts:

  1. You're creating new constraints here. Generally you will adjust the constant of an existing constraint. Or, if necessary, you will remove the old constraint, and add a new one.

  2. When changing an existing constraint, there are two approaches. One is to iterate through the view's constraints, finding the one in question, and then adjusting the constant for that. The other approach (and far easier when the view is one that you added in Interface Builder) is to create an IBOutlet for the constraint right in Interface Builder. Then you can animate that.

    • This answer, while focusing on a another problem, illustrates the process of creating an IBOutlet for a constraint and then animating the changing of its value.

    • If you have to iterate through the constraints looking for constraints (e.g. picking out one constraint out of a whole collection that you may have created with visual format language, for example), then the replaceLeadingAndTopWithCenterConstraints illustrated in this answer might be a helpful example of how you find a constraint on an existing view. This particular example is removing a constraint and adding a new one, but you could, just as easily, once you find the existing constraint, adjust its constant.


I'm not entirely sure of the UX you're going for, but let's say you have a panel you want to slide off to the left of the screen:

  1. The first issue is what constraints you have defined: In this example, I'd have four constraints defined, three to the superview (namely top, bottom, and left, but not the right) and a width constraint. And if you're doing this in IB, it can be finicky (e.g. you cannot remove the right constraint until you add the width constraint). Just make sure that the constraints are fully defined, but minimally so.

  2. Then I can animate the sliding of the panel off screen just by changing the constant of the left constraint:

    - (IBAction)touchUpInsideButton:(id)sender
    {
        self.offscreen = !self.offscreen;
    
        [UIView animateWithDuration:0.5
                         animations:^{
                             self.panelLeftConstraint.constant = ([self isOffscreen] ? -kHowMuchToSlideOffToLeft : 0.0);
                             [self.view layoutIfNeeded];
                         }];
    }
    
  3. You can see now why it was important to make sure that there was no right constraint defined. Because if I adjusted the left constraint while I had a width constraint and a right constraint defined, then it would become unsatisfiable. By making sure the constraints were minimally defined (though unambiguously defined) at the start, I can change the left constraint and the view now animates like I wanted it to.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for the answer, I will delve through these...I also remove all the constraints before adding the new ones...Although i do it in the init function... – Genhain May 21 '13 at 04:42
  • 1
    @Genhain I think it's much easier to change constraints than adding and removing. And be very careful which constraints you have defined for the view in the first place. For example, if I was going to slide it off to the left, I'd have it defined with top, bottom, left, and width (but not right!), and then I can animate by changing the constant of the left constraint. See revised answer. I must confess that I wasn't able to decipher what your desired UX was from your code sample, so if you clarify what you're trying to accomplish, I might be able to be more helpful. – Rob May 21 '13 at 05:37
  • Great answer, but point 2 in your first list is wrong. Only layoutIfNeeded has to be inside the animation block. – jrturton May 21 '13 at 06:59
  • @jrturton Quite right. thanks. I'm always nervous about things that reapply constraints themselves, but you're right that this is captured correctly by the `layoutIfNeeded` and I've removed that point. – Rob May 21 '13 at 07:26
  • I know this is quite late as a reply, but i managed to get it working, i just had to quite painstakingly fiddle, trial and error constraints...I did however forego animating the constraints...The old animation context seems to work anyway. and attempting to animate the correct constraints was not getting me anywhere, i should probably do some reading up on this...But thanks anyway. – Genhain May 24 '13 at 04:55
  • 1
    Adding the [self.view layoutIfNeeded]; inside the animation block fixed the apparently non-functional animation. – Johan Sep 07 '13 at 17:10