123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- //
- // 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;
- } copy];
- }
- - (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;
- } copy];
- }
- #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;
- } copy];
- }
- #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;
- }
- } copy];
- }
- #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;
- } copy];
- }
- #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
|