New file |
| | |
| | | // |
| | | // MASViewConstraint.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)setInset:(CGFloat)inset { |
| | | [self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}]; |
| | | } |
| | | |
| | | - (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 |