| // | 
| //  MASConstraint.m | 
| //  Masonry | 
| // | 
| //  Created by Jonas Budelmann on 20/07/13. | 
| //  Copyright (c) 2013 cloudling. All rights reserved. | 
| // | 
|   | 
| #import "MASViewConstraint.h" | 
| #import "MASConstraint+Private.h" | 
| #import "MASCompositeConstraint.h" | 
| #import "MASLayoutConstraint.h" | 
| #import "View+MASAdditions.h" | 
| #import <objc/runtime.h> | 
|   | 
| @interface MAS_VIEW (MASConstraints) | 
|   | 
| @property (nonatomic, readonly) NSMutableSet *mas_installedConstraints; | 
|   | 
| @end | 
|   | 
| @implementation MAS_VIEW (MASConstraints) | 
|   | 
| static char kInstalledConstraintsKey; | 
|   | 
| - (NSMutableSet *)mas_installedConstraints { | 
|     NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey); | 
|     if (!constraints) { | 
|         constraints = [NSMutableSet set]; | 
|         objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | 
|     } | 
|     return constraints; | 
| } | 
|   | 
| @end | 
|   | 
|   | 
| @interface MASViewConstraint () | 
|   | 
| @property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute; | 
| @property (nonatomic, weak) MAS_VIEW *installedView; | 
| @property (nonatomic, weak) MASLayoutConstraint *layoutConstraint; | 
| @property (nonatomic, assign) NSLayoutRelation layoutRelation; | 
| @property (nonatomic, assign) MASLayoutPriority layoutPriority; | 
| @property (nonatomic, assign) CGFloat layoutMultiplier; | 
| @property (nonatomic, assign) CGFloat layoutConstant; | 
| @property (nonatomic, assign) BOOL hasLayoutRelation; | 
| @property (nonatomic, strong) id mas_key; | 
| @property (nonatomic, assign) BOOL useAnimator; | 
|   | 
| @end | 
|   | 
| @implementation MASViewConstraint | 
|   | 
| - (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute { | 
|     self = [super init]; | 
|     if (!self) return nil; | 
|      | 
|     _firstViewAttribute = firstViewAttribute; | 
|     self.layoutPriority = MASLayoutPriorityRequired; | 
|     self.layoutMultiplier = 1; | 
|      | 
|     return self; | 
| } | 
|   | 
| #pragma mark - NSCoping | 
|   | 
| - (id)copyWithZone:(NSZone __unused *)zone { | 
|     MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute]; | 
|     constraint.layoutConstant = self.layoutConstant; | 
|     constraint.layoutRelation = self.layoutRelation; | 
|     constraint.layoutPriority = self.layoutPriority; | 
|     constraint.layoutMultiplier = self.layoutMultiplier; | 
|     constraint.delegate = self.delegate; | 
|     return constraint; | 
| } | 
|   | 
| #pragma mark - Public | 
|   | 
| + (NSArray *)installedConstraintsForView:(MAS_VIEW *)view { | 
|     return [view.mas_installedConstraints allObjects]; | 
| } | 
|   | 
| #pragma mark - Private | 
|   | 
| - (void)setLayoutConstant:(CGFloat)layoutConstant { | 
|     _layoutConstant = layoutConstant; | 
|   | 
| #if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV) | 
|     if (self.useAnimator) { | 
|         [self.layoutConstraint.animator setConstant:layoutConstant]; | 
|     } else { | 
|         self.layoutConstraint.constant = layoutConstant; | 
|     } | 
| #else | 
|     self.layoutConstraint.constant = layoutConstant; | 
| #endif | 
| } | 
|   | 
| - (void)setLayoutRelation:(NSLayoutRelation)layoutRelation { | 
|     _layoutRelation = layoutRelation; | 
|     self.hasLayoutRelation = YES; | 
| } | 
|   | 
| - (BOOL)supportsActiveProperty { | 
|     return [self.layoutConstraint respondsToSelector:@selector(isActive)]; | 
| } | 
|   | 
| - (BOOL)isActive { | 
|     BOOL active = YES; | 
|     if ([self supportsActiveProperty]) { | 
|         active = [self.layoutConstraint isActive]; | 
|     } | 
|   | 
|     return active; | 
| } | 
|   | 
| - (BOOL)hasBeenInstalled { | 
|     return (self.layoutConstraint != nil) && [self isActive]; | 
| } | 
|   | 
| - (void)setSecondViewAttribute:(id)secondViewAttribute { | 
|     if ([secondViewAttribute isKindOfClass:NSValue.class]) { | 
|         [self setLayoutConstantWithValue:secondViewAttribute]; | 
|     } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) { | 
|         _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute]; | 
|     } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) { | 
|         _secondViewAttribute = secondViewAttribute; | 
|     } else { | 
|         NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute); | 
|     } | 
| } | 
|   | 
| #pragma mark - NSLayoutConstraint multiplier proxies | 
|   | 
| - (MASConstraint * (^)(CGFloat))multipliedBy { | 
|     return ^id(CGFloat multiplier) { | 
|         NSAssert(!self.hasBeenInstalled, | 
|                  @"Cannot modify constraint multiplier after it has been installed"); | 
|          | 
|         self.layoutMultiplier = multiplier; | 
|         return self; | 
|     }; | 
| } | 
|   | 
|   | 
| - (MASConstraint * (^)(CGFloat))dividedBy { | 
|     return ^id(CGFloat divider) { | 
|         NSAssert(!self.hasBeenInstalled, | 
|                  @"Cannot modify constraint multiplier after it has been installed"); | 
|   | 
|         self.layoutMultiplier = 1.0/divider; | 
|         return self; | 
|     }; | 
| } | 
|   | 
| #pragma mark - MASLayoutPriority proxy | 
|   | 
| - (MASConstraint * (^)(MASLayoutPriority))priority { | 
|     return ^id(MASLayoutPriority priority) { | 
|         NSAssert(!self.hasBeenInstalled, | 
|                  @"Cannot modify constraint priority after it has been installed"); | 
|          | 
|         self.layoutPriority = priority; | 
|         return self; | 
|     }; | 
| } | 
|   | 
| #pragma mark - NSLayoutRelation proxy | 
|   | 
| - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { | 
|     return ^id(id attribute, NSLayoutRelation relation) { | 
|         if ([attribute isKindOfClass:NSArray.class]) { | 
|             NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation"); | 
|             NSMutableArray *children = NSMutableArray.new; | 
|             for (id attr in attribute) { | 
|                 MASViewConstraint *viewConstraint = [self copy]; | 
|                 viewConstraint.layoutRelation = relation; | 
|                 viewConstraint.secondViewAttribute = attr; | 
|                 [children addObject:viewConstraint]; | 
|             } | 
|             MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; | 
|             compositeConstraint.delegate = self.delegate; | 
|             [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint]; | 
|             return compositeConstraint; | 
|         } else { | 
|             NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation"); | 
|             self.layoutRelation = relation; | 
|             self.secondViewAttribute = attribute; | 
|             return self; | 
|         } | 
|     }; | 
| } | 
|   | 
| #pragma mark - Semantic properties | 
|   | 
| - (MASConstraint *)with { | 
|     return self; | 
| } | 
|   | 
| - (MASConstraint *)and { | 
|     return self; | 
| } | 
|   | 
| #pragma mark - attribute chaining | 
|   | 
| - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { | 
|     NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); | 
|   | 
|     return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; | 
| } | 
|   | 
| #pragma mark - Animator proxy | 
|   | 
| #if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV) | 
|   | 
| - (MASConstraint *)animator { | 
|     self.useAnimator = YES; | 
|     return self; | 
| } | 
|   | 
| #endif | 
|   | 
| #pragma mark - debug helpers | 
|   | 
| - (MASConstraint * (^)(id))key { | 
|     return ^id(id key) { | 
|         self.mas_key = key; | 
|         return self; | 
|     }; | 
| } | 
|   | 
| #pragma mark - NSLayoutConstraint constant setters | 
|   | 
| - (void)setInsets:(MASEdgeInsets)insets { | 
|     NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute; | 
|     switch (layoutAttribute) { | 
|         case NSLayoutAttributeLeft: | 
|         case NSLayoutAttributeLeading: | 
|             self.layoutConstant = insets.left; | 
|             break; | 
|         case NSLayoutAttributeTop: | 
|             self.layoutConstant = insets.top; | 
|             break; | 
|         case NSLayoutAttributeBottom: | 
|             self.layoutConstant = -insets.bottom; | 
|             break; | 
|         case NSLayoutAttributeRight: | 
|         case NSLayoutAttributeTrailing: | 
|             self.layoutConstant = -insets.right; | 
|             break; | 
|         default: | 
|             break; | 
|     } | 
| } | 
|   | 
| - (void)setOffset:(CGFloat)offset { | 
|     self.layoutConstant = offset; | 
| } | 
|   | 
| - (void)setSizeOffset:(CGSize)sizeOffset { | 
|     NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute; | 
|     switch (layoutAttribute) { | 
|         case NSLayoutAttributeWidth: | 
|             self.layoutConstant = sizeOffset.width; | 
|             break; | 
|         case NSLayoutAttributeHeight: | 
|             self.layoutConstant = sizeOffset.height; | 
|             break; | 
|         default: | 
|             break; | 
|     } | 
| } | 
|   | 
| - (void)setCenterOffset:(CGPoint)centerOffset { | 
|     NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute; | 
|     switch (layoutAttribute) { | 
|         case NSLayoutAttributeCenterX: | 
|             self.layoutConstant = centerOffset.x; | 
|             break; | 
|         case NSLayoutAttributeCenterY: | 
|             self.layoutConstant = centerOffset.y; | 
|             break; | 
|         default: | 
|             break; | 
|     } | 
| } | 
|   | 
| #pragma mark - MASConstraint | 
|   | 
| - (void)activate { | 
|     [self install]; | 
| } | 
|   | 
| - (void)deactivate { | 
|     [self uninstall]; | 
| } | 
|   | 
| - (void)install { | 
|     if (self.hasBeenInstalled) { | 
|         return; | 
|     } | 
|      | 
|     if ([self supportsActiveProperty] && self.layoutConstraint) { | 
|         self.layoutConstraint.active = YES; | 
|         [self.firstViewAttribute.view.mas_installedConstraints addObject:self]; | 
|         return; | 
|     } | 
|      | 
|     MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item; | 
|     NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute; | 
|     MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item; | 
|     NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute; | 
|   | 
|     // alignment attributes must have a secondViewAttribute | 
|     // therefore we assume that is refering to superview | 
|     // eg make.left.equalTo(@10) | 
|     if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) { | 
|         secondLayoutItem = self.firstViewAttribute.view.superview; | 
|         secondLayoutAttribute = firstLayoutAttribute; | 
|     } | 
|      | 
|     MASLayoutConstraint *layoutConstraint | 
|         = [MASLayoutConstraint constraintWithItem:firstLayoutItem | 
|                                         attribute:firstLayoutAttribute | 
|                                         relatedBy:self.layoutRelation | 
|                                            toItem:secondLayoutItem | 
|                                         attribute:secondLayoutAttribute | 
|                                        multiplier:self.layoutMultiplier | 
|                                          constant:self.layoutConstant]; | 
|      | 
|     layoutConstraint.priority = self.layoutPriority; | 
|     layoutConstraint.mas_key = self.mas_key; | 
|      | 
|     if (self.secondViewAttribute.view) { | 
|         MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view]; | 
|         NSAssert(closestCommonSuperview, | 
|                  @"couldn't find a common superview for %@ and %@", | 
|                  self.firstViewAttribute.view, self.secondViewAttribute.view); | 
|         self.installedView = closestCommonSuperview; | 
|     } else if (self.firstViewAttribute.isSizeAttribute) { | 
|         self.installedView = self.firstViewAttribute.view; | 
|     } else { | 
|         self.installedView = self.firstViewAttribute.view.superview; | 
|     } | 
|   | 
|   | 
|     MASLayoutConstraint *existingConstraint = nil; | 
|     if (self.updateExisting) { | 
|         existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint]; | 
|     } | 
|     if (existingConstraint) { | 
|         // just update the constant | 
|         existingConstraint.constant = layoutConstraint.constant; | 
|         self.layoutConstraint = existingConstraint; | 
|     } else { | 
|         [self.installedView addConstraint:layoutConstraint]; | 
|         self.layoutConstraint = layoutConstraint; | 
|         [firstLayoutItem.mas_installedConstraints addObject:self]; | 
|     } | 
| } | 
|   | 
| - (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint { | 
|     // check if any constraints are the same apart from the only mutable property constant | 
|   | 
|     // go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints | 
|     // and they are likely to be added first. | 
|     for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) { | 
|         if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue; | 
|         if (existingConstraint.firstItem != layoutConstraint.firstItem) continue; | 
|         if (existingConstraint.secondItem != layoutConstraint.secondItem) continue; | 
|         if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue; | 
|         if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue; | 
|         if (existingConstraint.relation != layoutConstraint.relation) continue; | 
|         if (existingConstraint.multiplier != layoutConstraint.multiplier) continue; | 
|         if (existingConstraint.priority != layoutConstraint.priority) continue; | 
|   | 
|         return (id)existingConstraint; | 
|     } | 
|     return nil; | 
| } | 
|   | 
| - (void)uninstall { | 
|     if ([self supportsActiveProperty]) { | 
|         self.layoutConstraint.active = NO; | 
|         [self.firstViewAttribute.view.mas_installedConstraints removeObject:self]; | 
|         return; | 
|     } | 
|      | 
|     [self.installedView removeConstraint:self.layoutConstraint]; | 
|     self.layoutConstraint = nil; | 
|     self.installedView = nil; | 
|      | 
|     [self.firstViewAttribute.view.mas_installedConstraints removeObject:self]; | 
| } | 
|   | 
| @end |