diff --git a/Masonry/MASCompositeConstraint.m b/Masonry/MASCompositeConstraint.m index 7e2db36..5b74fb6 100644 --- a/Masonry/MASCompositeConstraint.m +++ b/Masonry/MASCompositeConstraint.m @@ -87,16 +87,6 @@ }; } -#pragma mark - Semantic properties - -- (MASConstraint *)with { - return self; -} - -- (MASConstraint *)and { - return self; -} - #pragma mark - attribute chaining - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { @@ -104,50 +94,6 @@ return self; } -- (MASConstraint *)left { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft]; -} - -- (MASConstraint *)top { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; -} - -- (MASConstraint *)right { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight]; -} - -- (MASConstraint *)bottom { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom]; -} - -- (MASConstraint *)leading { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading]; -} - -- (MASConstraint *)trailing { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing]; -} - -- (MASConstraint *)width { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth]; -} - -- (MASConstraint *)height { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight]; -} - -- (MASConstraint *)centerX { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX]; -} - -- (MASConstraint *)centerY { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY]; -} - -- (MASConstraint *)baseline { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline]; -} - #pragma mark - Animator proxy #if TARGET_OS_MAC && !TARGET_OS_IPHONE diff --git a/Masonry/MASConstraint+Private.h b/Masonry/MASConstraint+Private.h index f88b626..a8ff253 100644 --- a/Masonry/MASConstraint+Private.h +++ b/Masonry/MASConstraint+Private.h @@ -45,6 +45,11 @@ */ - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation; +/** + * Override to set a custom chaining behaviour + */ +- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute; + @end @@ -56,4 +61,6 @@ */ - (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint; +- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute; + @end \ No newline at end of file diff --git a/Masonry/MASConstraint.h b/Masonry/MASConstraint.h index f8a20c1..d1fdbfa 100644 --- a/Masonry/MASConstraint.h +++ b/Masonry/MASConstraint.h @@ -223,6 +223,4 @@ */ - (MASConstraint * (^)(id offset))mas_offset; -- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute; - @end diff --git a/Masonry/MASConstraint.m b/Masonry/MASConstraint.m index 5e77dd0..55413bb 100644 --- a/Masonry/MASConstraint.m +++ b/Masonry/MASConstraint.m @@ -8,7 +8,7 @@ #import "MASConstraint.h" #import "MASConstraint+Private.h" -#define methodNotImplemented() \ +#define MASMethodNotImplemented() \ @throw [NSException exceptionWithName:NSInternalInconsistencyException \ reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \ userInfo:nil] @@ -154,58 +154,88 @@ return self; } -#pragma mark - Abstract +- (MASConstraint *)and { + return self; +} -- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { methodNotImplemented(); } +#pragma mark - Chaining -- (MASConstraint * (^)(CGFloat divider))dividedBy { methodNotImplemented(); } +- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { + MASMethodNotImplemented(); +} -- (MASConstraint * (^)(MASLayoutPriority priority))priority { methodNotImplemented(); } +- (MASConstraint *)left { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft]; +} + +- (MASConstraint *)top { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; +} + +- (MASConstraint *)right { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight]; +} -- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { methodNotImplemented(); } +- (MASConstraint *)bottom { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom]; +} -- (MASConstraint *)and { methodNotImplemented(); } +- (MASConstraint *)leading { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading]; +} -- (MASConstraint *)left { methodNotImplemented(); } +- (MASConstraint *)trailing { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing]; +} -- (MASConstraint *)top { methodNotImplemented(); } +- (MASConstraint *)width { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth]; +} -- (MASConstraint *)right { methodNotImplemented(); } +- (MASConstraint *)height { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight]; +} -- (MASConstraint *)bottom { methodNotImplemented(); } +- (MASConstraint *)centerX { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX]; +} -- (MASConstraint *)leading { methodNotImplemented(); } +- (MASConstraint *)centerY { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY]; +} -- (MASConstraint *)trailing { methodNotImplemented(); } +- (MASConstraint *)baseline { + return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline]; +} -- (MASConstraint *)width { methodNotImplemented(); } +#pragma mark - Abstract -- (MASConstraint *)height { methodNotImplemented(); } +- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); } -- (MASConstraint *)centerX { methodNotImplemented(); } +- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); } -- (MASConstraint *)centerY { methodNotImplemented(); } +- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); } -- (MASConstraint *)baseline { methodNotImplemented(); } +- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); } -- (MASConstraint * (^)(id key))key { methodNotImplemented(); } +- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); } -- (void)setInsets:(MASEdgeInsets)insets { methodNotImplemented(); } +- (void)setInsets:(MASEdgeInsets)insets { MASMethodNotImplemented(); } -- (void)setSizeOffset:(CGSize)sizeOffset { methodNotImplemented(); } +- (void)setSizeOffset:(CGSize)sizeOffset { MASMethodNotImplemented(); } -- (void)setCenterOffset:(CGPoint)centerOffset { methodNotImplemented(); } +- (void)setCenterOffset:(CGPoint)centerOffset { MASMethodNotImplemented(); } -- (void)setOffset:(CGFloat)offset { methodNotImplemented(); } +- (void)setOffset:(CGFloat)offset { MASMethodNotImplemented(); } #if TARGET_OS_MAC && !TARGET_OS_IPHONE -- (MASConstraint *)animator { methodNotImplemented(); } +- (MASConstraint *)animator { MASMethodNotImplemented(); } #endif -- (void)install { methodNotImplemented(); } +- (void)install { MASMethodNotImplemented(); } -- (void)uninstall { methodNotImplemented(); } +- (void)uninstall { MASMethodNotImplemented(); } @end diff --git a/Masonry/MASViewConstraint.m b/Masonry/MASViewConstraint.m index d66216a..e2f5b9c 100644 --- a/Masonry/MASViewConstraint.m +++ b/Masonry/MASViewConstraint.m @@ -190,51 +190,6 @@ static char kInstalledConstraintsKey; #pragma mark - attribute chaining - -- (MASConstraint *)left { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft]; -} - -- (MASConstraint *)top { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; -} - -- (MASConstraint *)right { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight]; -} - -- (MASConstraint *)bottom { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom]; -} - -- (MASConstraint *)leading { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading]; -} - -- (MASConstraint *)trailing { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing]; -} - -- (MASConstraint *)width { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth]; -} - -- (MASConstraint *)height { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight]; -} - -- (MASConstraint *)centerX { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX]; -} - -- (MASConstraint *)centerY { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY]; -} - -- (MASConstraint *)baseline { - return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline]; -} - - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); diff --git a/Tests/MasonryTests-Prefix.pch b/Tests/MasonryTests-Prefix.pch index 8fb4b31..cd1b19d 100644 --- a/Tests/MasonryTests-Prefix.pch +++ b/Tests/MasonryTests-Prefix.pch @@ -5,9 +5,9 @@ #ifdef __OBJC__ #import <Foundation/Foundation.h> + #define EXP_SHORTHAND #import "XCTest+Spec.h" - #define EXP_SHORTHAND #import "Expecta.h" #import "MASUtilities.h" diff --git a/Tests/Specs/MASCompositeConstraintSpec.m b/Tests/Specs/MASCompositeConstraintSpec.m index 2c2b7a4..41878ba 100644 --- a/Tests/Specs/MASCompositeConstraintSpec.m +++ b/Tests/Specs/MASCompositeConstraintSpec.m @@ -152,4 +152,25 @@ SpecBegin(MASCompositeConstraint) { expect(superview.constraints).to.haveCountOf(0); } + +- (void)testAttributeChainingShouldCallDelegate { + NSArray *children = @[ + [[MASViewConstraint alloc] initWithFirstViewAttribute:view.mas_left], + [[MASViewConstraint alloc] initWithFirstViewAttribute:view.mas_right] + ]; + composite = [[MASCompositeConstraint alloc] initWithChildren:children]; + composite.delegate = delegate; + expect(composite.childConstraints.count).to.equal(2); + + MASConstraint *result = (id)composite.and.bottom; + expect(result).to.beIdenticalTo(composite); + expect(delegate.chainedConstraints).to.equal(@[composite]); + expect(composite.childConstraints.count).to.equal(3); + + MASViewConstraint *newChild = composite.childConstraints[2]; + + expect(newChild.firstViewAttribute.layoutAttribute).to.equal(@(NSLayoutAttributeBottom)); + expect(newChild.delegate).to.beIdenticalTo(composite); +} + SpecEnd \ No newline at end of file diff --git a/Tests/Specs/MASConstraintDelegateMock.h b/Tests/Specs/MASConstraintDelegateMock.h index db24a05..e52c6cb 100644 --- a/Tests/Specs/MASConstraintDelegateMock.h +++ b/Tests/Specs/MASConstraintDelegateMock.h @@ -11,5 +11,6 @@ @interface MASConstraintDelegateMock : NSObject <MASConstraintDelegate> @property (nonatomic, strong) NSMutableArray *constraints; +@property (nonatomic, strong) NSMutableArray *chainedConstraints; @end diff --git a/Tests/Specs/MASConstraintDelegateMock.m b/Tests/Specs/MASConstraintDelegateMock.m index 27c93e1..59f94a2 100644 --- a/Tests/Specs/MASConstraintDelegateMock.m +++ b/Tests/Specs/MASConstraintDelegateMock.m @@ -7,6 +7,7 @@ // #import "MASConstraintDelegateMock.h" +#import "MASViewConstraint.h" @implementation MASConstraintDelegateMock @@ -15,6 +16,7 @@ if (!self) return nil; self.constraints = NSMutableArray.new; + self.chainedConstraints = NSMutableArray.new; return self; } @@ -23,4 +25,11 @@ [self.constraints replaceObjectAtIndex:[self.constraints indexOfObject:constraint] withObject:replacementConstraint]; } +- (id)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { + [self.chainedConstraints addObject:constraint]; + + MASViewConstraint *viewConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:[[MASViewAttribute alloc] initWithView:nil layoutAttribute:layoutAttribute]]; + return viewConstraint; +} + @end diff --git a/Tests/Specs/MASConstraintMakerSpec.m b/Tests/Specs/MASConstraintMakerSpec.m index 6acbbf2..a4794d1 100644 --- a/Tests/Specs/MASConstraintMakerSpec.m +++ b/Tests/Specs/MASConstraintMakerSpec.m @@ -209,4 +209,46 @@ SpecBegin(MASConstraintMaker) { expect(maker.centerY).notTo.beIdenticalTo(maker.centerY); } +- (void)testAttributeChainingWithComposite { + composite = (MASCompositeConstraint *)maker.size; + + expect(maker.constraints.count).to.equal(1); + expect(composite.childConstraints.count).to.equal(2); + composite = (id)composite.left; + expect(maker.constraints.count).to.equal(1); + expect(composite.childConstraints.count).to.equal(3); + + + MASViewConstraint *viewConstraint = composite.childConstraints[2]; + expect(viewConstraint.firstViewAttribute.view).to.beIdenticalTo(maker.view); + expect(viewConstraint.firstViewAttribute.layoutAttribute).to.equal(NSLayoutAttributeLeft); + expect(viewConstraint.delegate).to.beIdenticalTo(composite); +} + +- (void)testAttributeChainingWithViewConstraint { + MASViewConstraint *viewConstraint = (MASViewConstraint *)maker.width; + expect(maker.constraints.count).to.equal(1); + expect(viewConstraint).to.beIdenticalTo(maker.constraints[0]); + expect(viewConstraint.delegate).to.beIdenticalTo(maker); + + composite = (id)viewConstraint.height; + expect(composite).to.beKindOf(MASCompositeConstraint.class); + + expect(maker.constraints.count).to.equal(1); + expect(composite).to.beIdenticalTo(maker.constraints[0]); + expect(composite.delegate).to.beIdenticalTo(maker); + expect(viewConstraint.delegate).to.beIdenticalTo(composite); + + MASViewConstraint *childConstraint = composite.childConstraints[0]; + expect(childConstraint.firstViewAttribute.view).to.beIdenticalTo(maker.view); + expect(childConstraint.firstViewAttribute.layoutAttribute).to.equal(NSLayoutAttributeWidth); + expect(childConstraint.delegate).to.beIdenticalTo(composite); + expect(childConstraint).to.beIdenticalTo(viewConstraint); + + childConstraint = composite.childConstraints[1]; + expect(childConstraint.firstViewAttribute.view).to.beIdenticalTo(maker.view); + expect(childConstraint.firstViewAttribute.layoutAttribute).to.equal(NSLayoutAttributeHeight); + expect(childConstraint.delegate).to.beIdenticalTo(composite); +} + SpecEnd \ No newline at end of file diff --git a/Tests/Specs/MASViewConstraintSpec.m b/Tests/Specs/MASViewConstraintSpec.m index 1d5bd73..f01d937 100644 --- a/Tests/Specs/MASViewConstraintSpec.m +++ b/Tests/Specs/MASViewConstraintSpec.m @@ -509,4 +509,19 @@ SpecBegin(MASViewConstraint) { expect(superview.constraints).to.haveCountOf(0); } +- (void)testAttributeChainingShouldNotHaveRelation { + MASViewAttribute *secondViewAttribute = otherView.mas_top; + constraint.lessThanOrEqualTo(secondViewAttribute); + + expect(^{ + id result = constraint.bottom; + }).to.raise(@"NSInternalInconsistencyException"); +} + +- (void)testAttributeChainingShouldCallDelegate { + MASViewConstraint *result = (id)constraint.and.bottom; + expect(result.firstViewAttribute.layoutAttribute).to.equal(@(NSLayoutAttributeBottom)); + expect(delegate.chainedConstraints).to.equal(@[constraint]); +} + SpecEnd \ No newline at end of file