5

Let's assume that I have this UIView:

enter image description here

with these relative constraints:

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *leftMarginConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topMarginConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *widthConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;

Okay now let's assume that when the user click on that UIButton the button should move to the bottom-right corner of the view. We can easily use two constraints defining bottom space between the button and the bottom layout guide and right space (trailing space) from the button and the right edge of the view.

The problem it's that the UIButton already had two constraints (left/top) AND two contraints defining its width and its height so we can't add two new constraints because they would conflict with the other ones.

Simple and common situation for an animation scenario, but it causing me some problems. Ideas?

EDIT

When the user taps on the UIButton, I need that the button:

  1. change its title to "Second"
  2. wait 1 second and move to the bottom-right corner (remove top and left margin constraints and add bottom and right margin constraints)
  3. change its title to "Third"
  4. wait 1 second and move to the top-right corner (remove bottom margin constraint and add top margin constraint)

Am I seriously bound to use this messy code?

@implementation ViewController
{
    NSLayoutConstraint *_topMarginConstraint;
    NSLayoutConstraint *_leftMarginConstraint;
    NSLayoutConstraint *_bottomMarginConstraint;
    NSLayoutConstraint *_rightMarginConstraint;
}

- (IBAction)buttonPressed:(id)sender
{
    UIButton *button = sender;

    // 1.
    [sender setTitle:@"Second" forState:UIControlStateNormal];

    // 2.
    double delayInSeconds = 1.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
        [button removeConstraints:@[self.leftMarginConstraint, self.topMarginConstraint]];
        _bottomMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                               attribute:NSLayoutAttributeBottom
                                                               relatedBy:0 toItem:button
                                                               attribute:NSLayoutAttributeBottom
                                                              multiplier:1
                                                                constant:20];
        [self.view addConstraint:_bottomMarginConstraint];

        _rightMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                              attribute:NSLayoutAttributeRight
                                                              relatedBy:0 toItem:button
                                                              attribute:NSLayoutAttributeRight
                                                             multiplier:1
                                                               constant:20];
        [self.view addConstraint:_rightMarginConstraint];

        [UIView animateWithDuration:1 animations:^{
            [self.view layoutIfNeeded];
        } completion:^(BOOL finished) {
            // 3.
            [sender setTitle:@"Third" forState:UIControlStateNormal];

            // 4.
            double delayInSeconds = 1.0;
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
                [button removeConstraint:_bottomMarginConstraint];
                _topMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                                       attribute:NSLayoutAttributeTop
                                                                       relatedBy:0 toItem:button
                                                                       attribute:NSLayoutAttributeTop
                                                                      multiplier:1
                                                                        constant:20];
                [UIView animateWithDuration:1 animations:^{
                    [self.view layoutIfNeeded];
                }];
            });
        }];
    });
}

Serious? :D

vzm
  • 2,440
  • 6
  • 28
  • 47
Fred Collins
  • 5,004
  • 14
  • 62
  • 111
  • Is there anyway to avoid the annoying exception `UIViewAlertForUnsatisfiableConstraints:` that occurs in between removal and addition of congruent constraints? – Glavid Mar 11 '15 at 20:17

3 Answers3

11

Delete the left and top constraints, add the bottom and right constraints, then call layoutIfNeeded in an animation block to animate to the new position. The code in the second method, move2 could be embedded in the completion block of move1, but I find it easier to read if you keep the two moves in separate methods (it does necessitate the addition of another property, bottomCon):

- (IBAction)move1:(UIButton *)sender {
    [sender setTitle:@"Second" forState:UIControlStateNormal];
    [sender layoutIfNeeded];
    [self.view removeConstraints:@[self.leftCon,self.topCon]];
    self.bottomCon = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.button attribute:NSLayoutAttributeBottom multiplier:1 constant:20];
    [self.view addConstraint:self.bottomCon];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.button attribute:NSLayoutAttributeRight multiplier:1 constant:20]];
    [UIView animateWithDuration:1 delay:1.0 options:0 animations:^{
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {
        [sender setTitle:@"Third" forState:UIControlStateNormal];
        [self move2];
    }];
}

-(void)move2 {
    [self.view removeConstraint:self.bottomCon];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.button attribute:NSLayoutAttributeTop multiplier:1 constant:-20]];
    [UIView animateWithDuration:1 delay:1.0 options:0 animations:^{
        [self.view layoutIfNeeded];
    } completion:nil];
}
rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • @FredCollins, well, it's more like you changed your question. There are other ways to do it with animateWithDuration:animations:completion: which wouldn't necessitate the use of dispatch_after, but I don't know whether that would be any less "messy". – rdelmar Nov 11 '13 at 07:26
  • @FredCollins, I've updated my answer to show how I would do it -- I think it's easier to read and understand this way. – rdelmar Nov 11 '13 at 16:05
  • Nice rdelmar, I'll try your in a while. No way to do it with animation keyframes because I need to change various constraints right? Thanks anyway. – Fred Collins Nov 11 '13 at 16:30
  • Instead of using `delay` argument of that method I've used `performSelector:withObject:afterDelay`. – Fred Collins Nov 12 '13 at 02:30
1

For animation you need to change constant of the left and top constraint instead of adding new constraint. As follows:

 self.mBtnTopConstraint.constant = yourval1;
    self.mBtmLeftConstraint.constant = yourval2;
    [yourbtn setNeedsUpdateConstraints];

    [UIView animateWithDuration:0.5 animations:^{

            [yourbtn layoutIfNeeded];

        } completion:^(BOOL isFinished){
        }];

Hope this helps.

Nuzhat Zari
  • 3,398
  • 2
  • 24
  • 36
-1

You can make a placeholder UIView with the ending constraints you want and swap the original constraints with the placeholder UIView constraints.

That way it keeps the layout constraints in the storyboard and keeps your code from being messy. It also gives you the added effect of seeing the end result of your animation in the preview section of xcode.

If use the cocoapod "SBP" to do this with just a couple lines. Here's a tutorial.

Arjay Waran
  • 433
  • 5
  • 9