23

I have a UIView subclass with a corresponding xib file. In my xib I have a NSLayoutConstraint property which I'm trying to animate. I have an animateIn method. The problem is that only the animateIn method works. When I try to update the constant again it simply stays at the previous value.

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *horizontalConstraint;

I'm trying to update the constant after a button press. But the constant doesn't seem to update after setting. It still logs 0 even after setting it to -500. I'm calling layoutIfNeeded but nothing happens.

// this works
- (void) animateIn { 
    [UIView animateWithDuration:1.0 delay:2.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        self.alpha = 1.0;
    } completion:^(BOOL finished) {

        [UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            self.horizontalConstraint.constant = 0;
            [self layoutIfNeeded];
        } completion:^(BOOL finished) {
        }];
    }];
}

// this does not work
- (IBAction)resume:(id)sender {
        self.horizontalConstraint.constant = -500;
        [self layoutIfNeeded];

        NSLog(@"%f",self.horizontalConstraint.constant); // this always stays 0
    }

UPDATE

It seems that my NSLayoutConstraint is (null) when I want to use it a second time. That would explain why it is not updating. How should I keep a reference to it?

Ramin Afshar
  • 989
  • 2
  • 18
  • 34

5 Answers5

54

You will need to call setNeedsUpdateConstraints method of corresponding UIView(control) of which your NSLayoutConstraint is present to update constraint.

For example of UIButton

self.buttonConstraint.constant = 55;
[self.btnTest setNeedsUpdateConstraints];

In your case

[self setNeedsUpdateConstraints];
Yogesh Suthar
  • 30,424
  • 18
  • 72
  • 100
  • 5
    This is incorrect. You are supposed to call `setNeedsUpdateConstraints` when you need to update them, and then change the `constant` inside the `updateViewConstraints` method that gets called automatically. Or, you can change the constant in the method that requires it (to keep the code linked more tightly) and then call `setNeedsLayout` on the constraint's view's `superview`. – Iulian Onofrei Mar 29 '17 at 13:12
  • 3
    I disagree with your characterization here. You *may* override `updateConstraints()` to facilitate a complex set of batch changes. But, from the [documentation](https://developer.apple.com/reference/uikit/uiview/1622512-updateconstraints): > It is almost always cleaner and easier to update a constraint immediately after the affecting change has occurred. ... > You should only override this method when changing constraints in place is too slow, or when a view is producing a number of redundant changes. – Stan Apr 28 '17 at 21:56
15

I ran into a problem where the first time I set the constraint constant in code the constant would be set but then after calling layoutIfNeeded(), such as this:

myConstraint.constant = newValue;
[view layoutIfNeeded]

the constant would go back to the original value! I could watch the value change back in the debugger.

I finally figured out that it was because, in the storyboard, I had set a variation on the constant. For example:

enter image description here

As soon as I removed the variation, the constraint constant value did not change back to the original value with layoutIfNeeded call.

CLK
  • 211
  • 3
  • 7
  • Thank you! You save my day! – alc77 May 18 '18 at 07:39
  • This was it for me too. Anyone figure out the correct way of doing _both_ ? (Keeping the variation, and also being able to programmatically change the constant in code somewhere/somehow) – xaphod Apr 24 '19 at 17:36
  • @xaphod I ran in to a similar problem. I removed the variation from the xib and ended up programmatically setting the constraint constant for the size class during a trait collection change. – duncanc4 Jun 21 '19 at 14:42
  • 1
    Amazing, solved my problem. Almost seems like an IB bug. – Pranav Kasetti Sep 02 '19 at 22:03
11

Are you sure this constraint is not de-activated somewhere? Setting the "active" property of a constraint to false causes the view hierarchy to remove the constraint. If you don’t also have a reference to it, the constraint object will be deleted from memory.

I had the same issue as you, and removed the "weak" so the constraint is now a strong property. Consequently, it's not set to nil when de-activated (because my view controller always has a strong pointer to it), and I can re-activate it and re-set its constant.

// NB: don't create these contraints outlets as "weak" if you intend to de-activate them, otherwise they would be set to nil when deactivated!
@IBOutlet private var captionViewHeightConstraint: NSLayoutConstraint!
Frederic Adda
  • 5,905
  • 4
  • 56
  • 71
5

when you finished your constraint changes just call:

[self setNeedsUpdateConstraints];
Bogdan Somlea
  • 604
  • 3
  • 15
-1

The horizontalConstraint property should be held by the view itself. As long as the view is alive, the property should not be nil (unless you set it to nil explicitly somewhere else in the code). Double check whether you set it to nil by mistake.

Gurunath Sripad
  • 1,308
  • 2
  • 14
  • 24