iosobjective-cxcodenslayoutconstraint

I was trying to update the height constraint of my view during an animation. Its not getting updated why..?


Here is how i have configured the profile view,

-(void)addProfileView
{
    if(!_profileView)
    {
        _profileView = [ProfileView new];
        _profileView.clipsToBounds = YES;
        _profileView.delegate=self;
        _profileView.alpha = 1.0;
        _profileView.opaque = YES;
        _profileView.translatesAutoresizingMaskIntoConstraints = false;
        [self.view addSubview:_profileView];
        NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:_profileView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:160];
        NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:_profileView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
        NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:_profileView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
        NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:_profileView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:-64];
        [NSLayoutConstraint activateConstraints:@[heightConstraint, widthConstraint, leftConstraint, topConstraint]];
    }
}

Now, why is the profile view's height not changing after the animation? Should I add the constraints to the superview? What's the difference between adding constraints directly to the superview and using [NSLayoutConstraint activateConstraints:@[]] ?

-(void)animateProfileViewBasedOnOffset:(BOOL)isTop
{
__block NSLayoutConstraint *heightConstraint;
    
    for (NSLayoutConstraint * constraint in [_profileView constraints]) {
        if (constraint.firstAnchor == NSLayoutAttributeHeight) {
            heightConstraint = constraint;
            break;
        }
    }
    
    [UIView animateWithDuration:0.3 animations:^{
        if (isTop) {
            heightConstraint.constant = 64;
        }
        else {
            heightConstraint.constant = 160;
        }
        [_profileView layoutIfNeeded];
        [self.view layoutIfNeeded];
    }completion:^(BOOL completion)
     {
         NULL;
     }];
}

The height of the profileView only changes when I add its constraint to the superview ([self.view addConstraints:@[]]). During the animation, I loop through all the constraints of the superview to find the specific constraint and update it.


Solution

  • You should check and confirm that you are, in fact, finding the Height constraint:

    for (NSLayoutConstraint * constraint in [_profileView constraints]) {
        if (constraint.firstAnchor == NSLayoutAttributeHeight) {
            heightConstraint = constraint;
            break;
        }
    }
    if (heightConstraint) {
        NSLog(@"Found Height Constraint: %@", heightConstraint);
    } else {
        NSLog(@"Did NOT find Height Constraint!");
    }
    

    That code (in my quick test) does NOT find the Height constraint, so of course, no animation.

    This code:

    for (NSLayoutConstraint *constraint in _profileView.constraints) {
        // Check if the constraint is a height constraint
        if (constraint.firstAttribute == NSLayoutAttributeHeight && constraint.firstItem == _profileView) {
            // Found the height constraint
            heightConstraint = constraint;
            break;
        }
    }
    if (heightConstraint) {
        NSLog(@"Found Height Constraint: %@", heightConstraint);
    } else {
        NSLog(@"Did NOT find Height Constraint!");
    }
    

    Does find the Height constraint, and the animation happens as expected.


    As a side note, you might want to look at the more modern way of setting up constraints. This matches your constraints, but I think you'd agree it is much more readable (and much easier to edit when needed):

    [NSLayoutConstraint activateConstraints:@[
        [_profileView.heightAnchor constraintEqualToConstant:160.0],
        [_profileView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
        [_profileView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor],
        [_profileView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:-64.0],
    ]];
    

    Edit

    As mentioned in comments by Matt, you are (almost always) better off adding a constraint property that you can update when desired.

    Here's an example of that approach:

    #import <UIKit/UIKit.h>
    
    @interface AnimSubViewController : UIViewController
    @end
    
    @interface AnimSubViewController ()
    @property (strong, nonatomic) UIView *profileView;
    @property (strong, nonatomic) NSLayoutConstraint *heightConstraint;
    @end
    
    @implementation AnimSubViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = UIColor.systemBackgroundColor;
        
        [self addProfileView];
    }
    
    -(void)addProfileView
    {
        if(!_profileView)
        {
            //_profileView = [ProfileView new];
            _profileView = [UIView new];
            _profileView.backgroundColor = UIColor.redColor;
            _profileView.clipsToBounds = YES;
            //_profileView.delegate=self;
            _profileView.alpha = 1.0;
            _profileView.opaque = YES;
            _profileView.translatesAutoresizingMaskIntoConstraints = false;
            [self.view addSubview:_profileView];
    
            // set the _heightConstraint property, so we can modify its .constant later
            _heightConstraint = [_profileView.heightAnchor constraintEqualToConstant:160.0];
            
            [NSLayoutConstraint activateConstraints:@[
                //[_profileView.heightAnchor constraintEqualToConstant:160.0],
                _heightConstraint,
                [_profileView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
                [_profileView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor],
                [_profileView.topAnchor constraintEqualToAnchor:self.view.topAnchor constant:-64.0],
            ]];
    
        }
    }
    
    -(void)animateProfileViewBasedOnOffset:(BOOL)isTop
    {
    
        [UIView animateWithDuration:0.3 animations:^{
            if (isTop) {
                self->_heightConstraint.constant = 64;
            }
            else {
                self->_heightConstraint.constant = 160;
            }
            // not needed
            //[self->_profileView layoutIfNeeded];
            
            [self.view layoutIfNeeded];
            
        } completion:^(BOOL completion) {
            NULL;
        }];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // hide/show _profileView on tap anywhere
        [self animateProfileViewBasedOnOffset:_heightConstraint.constant == 160.0];
    }
    
    @end