|
@@ -0,0 +1,406 @@
|
|
|
|
+//
|
|
|
|
+// 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]) {
|
|
|
|
+ MASViewAttribute *attr = secondViewAttribute;
|
|
|
|
+ if (attr.layoutAttribute == NSLayoutAttributeNotAnAttribute) {
|
|
|
|
+ _secondViewAttribute = [[MASViewAttribute alloc] initWithView:attr.view item:attr.item layoutAttribute:self.firstViewAttribute.layoutAttribute];;
|
|
|
|
+ } else {
|
|
|
|
+ _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
|