Reckoner
Reckoner

Reputation: 1041

Autolayout programmatically

I am kind of trying to get into the depths of auto layout.I have a decent knowledge of working in auto layout from storyboard.Also,I know how to use the NSLayoutConstraint class as well. Here is the problem:I have 2 views(redView and yellowView).From storyboard,I have already set the constraints of both the views.Now in my code,suppose I want to change the width of redView w.r.t the width of yellowView.So I have used the following code for that:

NSLayoutConstraint *layouts1 = [NSLayoutConstraint 
                               constraintWithItem:_redView 
                               attribute:NSLayoutAttributeWidth 
                               relatedBy:NSLayoutRelationEqual 
                               toItem:_yellowView 
                               attribute:NSLayoutAttributeWidth
                               multiplier:3.0f 
                               constant:0];
[self.view addConstraint:layouts1]; 

Now I run the code and though I get the expected output,it shows 'Unsatisfiable constraints' message at the console (which it should because multiple widths are being set).Now the question is :how could I get rid of that message? I have tried a couple of things but they are not working.Here is what I have tried:

  1. I did not set the constraints in the storyboard.Instead I directly worked with the below code.
  2. I tried to use the method 'layoutIfNeeded'.

Well I can write the whole auto layout code programmatically but since we have the privilege to set autolayout through storyboards,it is completely unnecessary.There must be some way just to update the constraints(set in storyboards) programmatically without getting any conflicts.

Upvotes: 0

Views: 579

Answers (3)

Vincent Joy
Vincent Joy

Reputation: 2678

MARK: - Find Constraints

- (NSLayoutConstraint *)myConstraintWithAttribute:(NSLayoutAttribute)attribute
{
    /* Find constraint with attribute in my constraints */
    __block NSLayoutConstraint *resultConstraint;
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop)
     {
         //  DebugLog(@"constraint %@", constraint);
         if ([NSStringFromClass([NSLayoutConstraint class]) isEqualToString:NSStringFromClass([constraint class])])
         {
             if (constraint.firstAttribute == attribute || constraint.secondAttribute == attribute)
             {
                 resultConstraint = constraint;
                 *stop = YES;
             }
         }
     }];

    return resultConstraint;
}


- (NSLayoutConstraint *)superviewConstraintWithAttribute:(NSLayoutAttribute)attribute
{
    /* Find constraint with attribute in my superview's constraints */
    __block NSLayoutConstraint *resultConstraint;
    [self.superview.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop)
     {
         if (constraint.firstItem == self && constraint.firstAttribute == attribute)
             //|| (constraint.secondItem == self && constraint.secondAttribute == attribute))
         {
             resultConstraint = constraint;
             *stop = YES;
         }
     }];

    return resultConstraint;
}


- (NSLayoutConstraint *)constraintWithAttribute:(NSLayoutAttribute)attribute
{
    /* Find constraint with attribute in my constraints */
    NSLayoutConstraint *resultConstraint = [self myConstraintWithAttribute:attribute];

    /* Find constraint with attribute in my superview's constraints */
    if (!resultConstraint)
    {
        resultConstraint = [self superviewConstraintWithAttribute:attribute];
    }
    return resultConstraint;
}


- (BOOL)removeConstraintWithAttribute:(NSLayoutAttribute)attribute
{
    NSLayoutConstraint *constraint = [self superviewConstraintWithAttribute:attribute];
    if (constraint)
    {
        [self.superview removeConstraint:constraint];
        return YES;
    }
    constraint = [self myConstraintWithAttribute:attribute];
    if (constraint)
    {
        [self removeConstraint:constraint];
        return YES;
    }
    return NO;
}

MARK: - Remove Constraints

- (void)removeMyConstraints
{
    /* Remove all my constraitns from superview */
    [self.superview removeConstraints:[self mySuperviewConstraints]];

    /* Remove my constraitns */

    [self removeConstraints:self.constraints];
}


- (NSArray *)mySuperviewConstraints
{
    NSMutableArray *mySuperviewConstraints = [NSMutableArray array];
    [self.superview.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop)
     {
         if (constraint.firstItem == self || constraint.secondItem == self)
         {
             [mySuperviewConstraints addObject:constraint];
         }
     }];
    return mySuperviewConstraints;
}


- (void)removeMyConstraintsButKeepMySubviewConstraints
{
    /* Remove all my constraitns from superview */
    [self.superview removeConstraints:[self mySuperviewConstraints]];

    /* Remove my constraitns */

    [self removeConstraints:[self myConstraints]];
}


- (NSArray *)myConstraints
{
    NSMutableArray *myConstraints = [NSMutableArray array];
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop)
     {
         if (constraint.firstItem == self && constraint.secondItem == nil)
         {
             [myConstraints addObject:constraint];
         }
     }];
    return myConstraints;
}

MARK: - Size Constraints

- (void)addWidthConstraint:(CGFloat)width
{
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
                                                                  attribute:NSLayoutAttributeWidth
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:nil
                                                                  attribute:0
                                                                 multiplier:1
                                                                   constant:width];
    [self addConstraint:constraint];
}

- (void)addWidthConstraintFromLabel:(UILabel *)label
                         withOffset:(CGFloat)offset
{
    NSDictionary *attributes = @{NSFontAttributeName : label.font};
    return [self addWidthConstraint:[label.text sizeWithAttributes:attributes].width + offset];
}

- (void)addHeightConstraint:(CGFloat)height
{
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
                                                                  attribute:NSLayoutAttributeHeight
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:nil
                                                                  attribute:0
                                                                 multiplier:1
                                                                   constant:height];
    [self addConstraint:constraint];
}

- (void)addMaximumHeightConstraint:(CGFloat)maxHeight
{
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
                                                                  attribute:NSLayoutAttributeHeight
                                                                  relatedBy:NSLayoutRelationLessThanOrEqual
                                                                     toItem:nil
                                                                  attribute:0
                                                                 multiplier:1
                                                                   constant:maxHeight];
    [self addConstraint:constraint];

}


- (void)addWidthConstraintFromImage:(UIImage *)image
{
    [self addWidthConstraint:image.size.width];
}


- (void)addHeightConstraintFromImage:(UIImage *)image
{
    [self addHeightConstraint:image.size.height];
}

MARK: - Center Contraints

- (void)addCenterConstraint:(UIView *)view
            centerDirection:(NSLayoutAttribute)centerDirection
                     offset:(CGFloat)offset
{
    UIView *viewItem = (view) ? view : self.superview;
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
                                                                  attribute:centerDirection
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:viewItem
                                                                  attribute:centerDirection
                                                                 multiplier:1
                                                                   constant:offset];
    [self.superview addConstraint:constraint];
}


- (void)addCenterXConstraint:(UIView *)view
{
    [self addCenterConstraint:view
              centerDirection:NSLayoutAttributeCenterX
                       offset:0];
}


- (void)addCenterYConstraint:(UIView *)view
{
    [self addCenterConstraint:view
              centerDirection:NSLayoutAttributeCenterY
                       offset:0];
}


- (void)addCenterXConstraint:(UIView *)view
                      offset:(CGFloat)offset
{
    [self addCenterConstraint:view
              centerDirection:NSLayoutAttributeCenterX
                       offset:offset];
}


- (void)addCenterYConstraint:(UIView *)view
                      offset:(CGFloat)offset
{
    [self addCenterConstraint:view
              centerDirection:NSLayoutAttributeCenterY
                       offset:offset];
}

MARK: - Edge Attach Constraints

- (void)addEdgeAttachConstraint:(UIView *)view
                       viewEdge:(NSLayoutAttribute)viewLayoutAttribute
                         offset:(CGFloat)offset
                           edge:(NSLayoutAttribute)layoutAttribute
{
    UIView *viewItem = (view) ? view : self.superview;

    /* Reverse offset for right side and bottom */
    CGFloat fixedOffset = offset;
    if (layoutAttribute == NSLayoutAttributeRight
        || layoutAttribute == NSLayoutAttributeBottom
        || layoutAttribute == NSLayoutAttributeTrailing)
    {
        fixedOffset = -offset;
    }

    /* Add contraint */
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
                                                                  attribute:layoutAttribute
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:viewItem
                                                                  attribute:viewLayoutAttribute
                                                                 multiplier:1
                                                                   constant:fixedOffset];
    [self.superview addConstraint:constraint];
}




- (void)addLeftEdgeAttachConstraint:(UIView *)view
                             offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:NSLayoutAttributeLeft
                           offset:offset
                             edge:NSLayoutAttributeLeft];
}


- (void)addRightEdgeAttachConstraint:(UIView *)view
                              offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:NSLayoutAttributeRight
                           offset:offset
                             edge:NSLayoutAttributeRight];
}


- (void)addTopEdgeAttachConstraint:(UIView *)view
                            offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:NSLayoutAttributeTop
                           offset:offset
                             edge:NSLayoutAttributeTop];
}


- (void)addBottomEdgeAttachConstraint:(UIView *)view
                               offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:NSLayoutAttributeBottom
                           offset:offset
                             edge:NSLayoutAttributeBottom];
}




- (void)addLeftEdgeAttachConstraint:(UIView *)view
{
    [self addLeftEdgeAttachConstraint:view
                               offset:0];
}


- (void)addRightEdgeAttachConstraint:(UIView *)view
{
    [self addRightEdgeAttachConstraint:view
                                offset:0];
}


- (void)addTopEdgeAttachConstraint:(UIView *)view
{
    [self addTopEdgeAttachConstraint:view
                              offset:0];
}


- (void)addBottomEdgeAttachConstraint:(UIView *)view
{
    [self addBottomEdgeAttachConstraint:view
                                 offset:0];
}




- (void)addEdgeAttachConstraints:(UIView *)view
                      leftOffset:(CGFloat)leftOffset
                     rightOffset:(CGFloat)rightOffset
                       topOffset:(CGFloat)topOffset
                    bottomOffset:(CGFloat)bottomOffset
{
    [self addLeftEdgeAttachConstraint:view
                               offset:leftOffset];
    [self addRightEdgeAttachConstraint:view
                                offset:rightOffset];
    [self addTopEdgeAttachConstraint:view
                              offset:topOffset];
    [self addBottomEdgeAttachConstraint:view
                                 offset:bottomOffset];
}


- (void)addEdgeAttachConstraints:(UIView *)view
{
    [self addLeftEdgeAttachConstraint:view];
    [self addRightEdgeAttachConstraint:view];
    [self addTopEdgeAttachConstraint:view];
    [self addBottomEdgeAttachConstraint:view];
}

MARK: - Edge Constraint To Different Edge

- (void)addLeftEdgeAttachConstraint:(UIView *)view
                           viewEdge:(NSLayoutAttribute)viewLayoutAttribute
                             offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:viewLayoutAttribute
                           offset:offset
                             edge:NSLayoutAttributeLeft];
}


- (void)addRightEdgeAttachConstraint:(UIView *)view
                            viewEdge:(NSLayoutAttribute)viewLayoutAttribute
                              offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:viewLayoutAttribute
                           offset:offset
                             edge:NSLayoutAttributeRight];
}


- (void)addTopEdgeAttachConstraint:(UIView *)view
                          viewEdge:(NSLayoutAttribute)viewLayoutAttribute
                            offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:viewLayoutAttribute
                           offset:offset
                             edge:NSLayoutAttributeTop];
}


- (void)addBottomEdgeAttachConstraint:(UIView *)view
                             viewEdge:(NSLayoutAttribute)viewLayoutAttribute
                               offset:(CGFloat)offset
{
    [self addEdgeAttachConstraint:view
                         viewEdge:viewLayoutAttribute
                           offset:offset
                             edge:NSLayoutAttributeBottom];
}

MARK: - Size Attach Constaints

- (void)addSizeAndSuperviewAttachConstraints:(NSString *)sizeConstraint
                                 firstOffset:(CGFloat)firstOffset
                                secondOffset:(CGFloat)secondOffset
                                   direction:(NSString *)direction
{
    NSDictionary *viewDict = NSDictionaryOfVariableBindings(self);
    NSString *visualFormatString;

    if (sizeConstraint)
    {
        visualFormatString = [NSString stringWithFormat:@"%@:|-%f-[self(%@)]-%f-|", direction, firstOffset, sizeConstraint, secondOffset];
    }
    else
    {
        visualFormatString = [NSString stringWithFormat:@"%@:|-%f-[self]-%f-|", direction, firstOffset, secondOffset];
    }

    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:visualFormatString
                                                                   options:0
                                                                   metrics:0
                                                                     views:viewDict];
    [self.superview addConstraints:constraints];
}


- (void)addWidthAndSuperviewAttachConstraints:(NSString *)widthConstraint
                                   leftOffset:(CGFloat)leftOffset
                                  rightOffset:(CGFloat)rightOffset
{
    [self addSizeAndSuperviewAttachConstraints:widthConstraint
                                   firstOffset:leftOffset
                                  secondOffset:rightOffset
                                     direction:@"H"];
}


- (void)addHeightAndSuperviewAttachConstraints:(NSString *)heightConstraint
                                     topOffset:(CGFloat)topOffset
                                  bottomOffset:(CGFloat)bottomOffset
{
    [self addSizeAndSuperviewAttachConstraints:heightConstraint
                                   firstOffset:topOffset
                                  secondOffset:bottomOffset
                                     direction:@"V"];
}

MARK: - Row & Column Layout Constraints

- (void)addLayoutConstraintsForMySubviews:(NSArray *)views
                              firstOffset:(CGFloat)firstOffset
                             secondOffset:(CGFloat)secondOffset
                            betweenOffset:(NSString *)betweenOffset
                                direction:(NSString *)direction
                                equalSize:(BOOL)equalSize
{
    /* Create viewDict and visualFormatString */
    NSMutableString *visualFormatString = [[NSMutableString alloc] initWithFormat:@"%@:|-%.0f-", direction, firstOffset];
    NSMutableDictionary *viewDict = [[NSMutableDictionary alloc] init];
    [views enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop)
     {
         NSString *viewName = [NSString stringWithFormat:@"view%i", idx];
         [viewDict setObject:view
                      forKey:viewName];

         if (idx < [views count] - 1)
         {
             /* Add each view */
             if (betweenOffset) /* Add offset between view */
             {
                 /* Add equal size to prev view for all but index 0 */
                 if (equalSize && idx > 0)
                 {
                     NSString *prevViewName = [NSString stringWithFormat:@"view%i", idx - 1];
                     [visualFormatString appendFormat:@"[%@(==%@)]-%@-", viewName, prevViewName, betweenOffset];
                 }
                 else
                 {
                     [visualFormatString appendFormat:@"[%@]-%@-", viewName, betweenOffset];
                 }
             }
             else /* No offset between views */
             {
                 /* Add equal size to prev view for all but index 0 */
                 if (equalSize && idx > 0)
                 {
                     NSString *prevViewName = [NSString stringWithFormat:@"view%i", idx - 1];
                     [visualFormatString appendFormat:@"[%@(==%@)]", viewName, prevViewName];
                 }
                 else
                 {
                     [visualFormatString appendFormat:@"[%@]", viewName];
                 }
             }
         }
         else
         {
             /* Add equal size to prev view for all but index 0 */
             if (equalSize && idx > 0)
             {
                 NSString *prevViewName = [NSString stringWithFormat:@"view%i", idx - 1];
                 [visualFormatString appendFormat:@"[%@(==%@)]-%.0f-|", viewName, prevViewName, secondOffset];
             }
             else
             {
                 [visualFormatString appendFormat:@"[%@]-%.0f-|", viewName, secondOffset];
             }
         }
     }];

    //    DebugLog(@"viewDict %@", viewDict);
    //    DebugLog(@"visualFormatString %@", visualFormatString);

    /* Add constraints */
    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:visualFormatString
                                                                   options:0
                                                                   metrics:0
                                                                     views:viewDict];
    [self addConstraints:constraints];
}


- (void)addRowLayoutConstraintsForMySubviews:(NSArray *)subviews
                                  leftOffset:(CGFloat)leftOffset
                                 rightOffset:(CGFloat)rightOffset
                               betweenOffset:(NSString *)betweenOffset
                                  equalWidth:(BOOL)equalWidth
{
    [self addLayoutConstraintsForMySubviews:subviews
                                firstOffset:leftOffset
                               secondOffset:rightOffset
                              betweenOffset:betweenOffset
                                  direction:@"H"
                                  equalSize:equalWidth];
}


- (void)addColumnLayoutConstraintsForMySubviews:(NSArray *)subviews
                                      topOffset:(CGFloat)topOffset
                                   bottomOffset:(CGFloat)bottomOffset
                                  betweenOffset:(NSString *)betweenOffset
                                    equalHeight:(BOOL)equalHeight
{
    [self addLayoutConstraintsForMySubviews:subviews
                                firstOffset:topOffset
                               secondOffset:bottomOffset
                              betweenOffset:betweenOffset
                                  direction:@"V"
                                  equalSize:equalHeight];
}

MARK: - Row & Column Equal Size Layout Constraints

- (void)addEqualSizeLayoutConstraintsForMySubviews:(NSArray *)views
                                       firstOffset:(CGFloat)firstOffset
                                      secondOffset:(CGFloat)secondOffset
                                     betweenOffset:(NSString *)betweenOffset
                                         direction:(NSString *)direction
{
    /* Create viewDict and visualFormatString */
    NSMutableString *visualFormatString = [[NSMutableString alloc] initWithFormat:@"%@:|-%.0f-", direction, firstOffset];
    NSMutableDictionary *viewDict = [[NSMutableDictionary alloc] init];
    [views enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop)
     {
         NSString *viewName = [NSString stringWithFormat:@"view%i", idx];
         [viewDict setObject:view
                      forKey:viewName];

         if (idx < [views count] - 1)
         {
             if (betweenOffset)
             {
                 [visualFormatString appendFormat:@"[%@]-%@-", viewName, betweenOffset];
             }
             else
             {
                 [visualFormatString appendFormat:@"[%@(>=40)]", viewName];
             }
         }
         else
         {
             [visualFormatString appendFormat:@"[%@(>=40)]-%.0f-|", viewName, secondOffset];
         }
     }];


    //    DebugLog(@"viewDict %@", viewDict);

    /* Add constraints */
    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view0]-2-[view1(==view0)]-2-[view2(==view1)]-2-[view3(==view2)]-2-[view4(==view3)]-2-[view5(==view4)]-0-|"
                                                                   options:0
                                                                   metrics:0
                                                                     views:viewDict];
    [self addConstraints:constraints];
}


- (void)addRowLayoutEqualWidthConstraintsForMySubviews:(NSArray *)subviews
                                            leftOffset:(CGFloat)leftOffset
                                           rightOffset:(CGFloat)rightOffset
                                         betweenOffset:(NSString *)betweenOffset
{
    [self addEqualSizeLayoutConstraintsForMySubviews:subviews
                                         firstOffset:leftOffset
                                        secondOffset:rightOffset
                                       betweenOffset:betweenOffset
                                           direction:@"H"];
}

Upvotes: 0

ullstrm
ullstrm

Reputation: 10160

Am I understanding you correctly when you want to edit an existing constraint programmatically? I.e. a constraint set from storyboard?

You can make a IBOutlet from the constraint, and set its constant-property programmatically. Then call layotIfNeeded.

Upvotes: 1

Reckoner
Reckoner

Reputation: 1041

Well I understand what I was trying to do.As I had already set the constraints in the storyboard,I need to remove that width constraint(in storyboard) to update it with a new constraint.SO I just created an IBOutlet of the width of my yellowView and then this code did the trick for me:

[_yellowView removeConstraint:_myConstraints];//_myConstraints is the outlet

Now I'm not facing any problem.Thanks a lot guys :-)

Upvotes: 0

Related Questions