Authored by Jonas Budelmann

added ability to remove constraint. refactored child constraints

... ... @@ -76,8 +76,10 @@
#pragma mark - MASConstraintDelegate
- (void)addConstraint:(id<MASConstraint>)constraint {
[self.delegate addConstraint:constraint];
- (void)constraint:(id<MASConstraint>)constraint shouldBeReplacedWithConstraint:(id<MASConstraint>)replacementConstraint {
int index = [self.childConstraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
#pragma mark - NSLayoutConstraint constant proxies
... ... @@ -165,7 +167,7 @@
- (id<MASConstraint> (^)(id))equalTo {
return ^id(id attr) {
for (id<MASConstraint> constraint in self.childConstraints) {
for (id<MASConstraint> constraint in self.childConstraints.copy) {
constraint.equalTo(attr);
}
return self;
... ... @@ -174,7 +176,7 @@
- (id<MASConstraint> (^)(id))greaterThanOrEqualTo {
return ^id(id attr) {
for (id<MASConstraint> constraint in self.childConstraints) {
for (id<MASConstraint> constraint in self.childConstraints.copy) {
constraint.greaterThanOrEqualTo(attr);
}
return self;
... ... @@ -183,7 +185,7 @@
- (id<MASConstraint> (^)(id))lessThanOrEqualTo {
return ^id(id attr) {
for (id<MASConstraint> constraint in self.childConstraints) {
for (id<MASConstraint> constraint in self.childConstraints.copy) {
constraint.lessThanOrEqualTo(attr);
}
return self;
... ... @@ -211,7 +213,16 @@
#pragma mark - MASConstraint
- (void)commit {
- (void)installConstraint {
for (id<MASConstraint> constraint in self.childConstraints) {
[constraint installConstraint];
}
}
- (void)uninstallConstraint {
for (id<MASConstraint> constraint in self.childConstraints) {
[constraint uninstallConstraint];
}
}
@end
... ...
... ... @@ -111,19 +111,23 @@ typedef float MASLayoutPriority;
@property (nonatomic, copy, readonly) id<MASConstraint> (^key)(id key);
/**
* Creates a NSLayoutConstraint. The constraint is added to the first view or the or the closest common superview of the first and second view.
* Creates a NSLayoutConstraint. The constraint is installed to the first view or the or the closest common superview of the first and second view.
*/
- (void)commit;
- (void)installConstraint;
/**
* Removes previously installed NSLayoutConstraint
*/
- (void)uninstallConstraint;
@end
@protocol MASConstraintDelegate <NSObject>
/**
* Notifies the delegate when the constraint is has the minimum set of properties.
*
* @param constraint a constraint that has at least a NSLayoutRelation and view
* Notifies the delegate when the constraint needs to be replaced with another constraint. For example
* A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
*/
- (void)addConstraint:(id<MASConstraint>)constraint;
- (void)constraint:(id<MASConstraint>)constraint shouldBeReplacedWithConstraint:(id<MASConstraint>)replacementConstraint;
@end
\ No newline at end of file
... ...
... ... @@ -32,15 +32,17 @@
- (void)commit {
for (id<MASConstraint> constraint in self.constraints) {
[constraint commit];
[constraint installConstraint];
}
[self.constraints removeAllObjects];
}
#pragma mark - MASConstraintDelegate
- (void)addConstraint:(id<MASConstraint>)constraint {
[self.constraints addObject:constraint];
- (void)constraint:(id<MASConstraint>)constraint shouldBeReplacedWithConstraint:(id<MASConstraint>)replacementConstraint {
int index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
#pragma mark - constraint properties
... ... @@ -49,6 +51,7 @@
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
... ... @@ -104,18 +107,21 @@
- (id<MASConstraint>)edges {
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithView:self.view type:MASCompositeConstraintTypeEdges];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
- (id<MASConstraint>)size {
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithView:self.view type:MASCompositeConstraintTypeSize];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
- (id<MASConstraint>)center {
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithView:self.view type:MASCompositeConstraintTypeCenter];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
@end
... ...
... ... @@ -14,6 +14,7 @@
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
@property (nonatomic, strong, readwrite) MASLayoutConstraint *layoutConstraint;
@property (nonatomic, weak) UIView *installedView;
@property (nonatomic, assign) NSLayoutRelation layoutRelation;
@property (nonatomic, assign) MASLayoutPriority layoutPriority;
@property (nonatomic, assign) CGFloat layoutMultiplier;
... ... @@ -60,7 +61,7 @@
self.hasLayoutRelation = YES;
}
- (BOOL)hasBeenCommitted {
- (BOOL)hasBeenInstalled {
return self.layoutConstraint != nil;
}
... ... @@ -146,8 +147,8 @@
- (id<MASConstraint> (^)(CGFloat))percent {
return ^id(CGFloat percent) {
NSAssert(!self.hasBeenCommitted,
@"Cannot modify constraint percent after it has been committed");
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint percent after it has been installed");
self.layoutMultiplier = percent;
return self;
... ... @@ -158,8 +159,8 @@
- (id<MASConstraint> (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
NSAssert(!self.hasBeenCommitted,
@"Cannot modify constraint priority after it has been committed");
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint priority after it has been installed");
self.layoutPriority = priority;
return self;
... ... @@ -197,16 +198,15 @@
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.secondViewAttribute = attr;
[viewConstraint.delegate addConstraint:viewConstraint];
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithView:self.firstViewAttribute.view children:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
[self.delegate addConstraint:self];
return self;
}
};
... ... @@ -241,8 +241,8 @@
#pragma mark - MASConstraint
- (void)commit {
NSAssert(!self.hasBeenCommitted, @"Cannot commit constraint more than once");
- (void)installConstraint {
NSAssert(!self.hasBeenInstalled, @"Cannot install constraint more than once");
UIView *firstLayoutItem = self.firstViewAttribute.view;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
... ... @@ -283,11 +283,18 @@
@"couldn't find a common superview for %@ and %@",
firstLayoutItem,
secondLayoutItem);
self.installedView = closestCommonSuperview;
[closestCommonSuperview addConstraint:self.layoutConstraint];
} else {
self.installedView = firstLayoutItem;
[firstLayoutItem addConstraint:self.layoutConstraint];
}
}
- (void)uninstallConstraint {
[self.installedView removeConstraint:self.layoutConstraint];
self.layoutConstraint = nil;
self.installedView = nil;
}
@end
... ...
... ... @@ -116,7 +116,7 @@ it(@"should complete children", ^{
expect(viewConstraint.layoutPriority).to.equal(MASLayoutPriorityDefaultLow);
});
it(@"should not remove on commit", ^{
it(@"should not remove on install", ^{
composite = [[MASCompositeConstraint alloc] initWithView:view type:MASCompositeConstraintTypeSize];
composite.delegate = delegate;
UIView *newView = UIView.new;
... ... @@ -125,11 +125,22 @@ it(@"should not remove on commit", ^{
//first equality statement
composite.equalTo(newView).sizeOffset(CGSizeMake(90, 30));
[composite commit];
[composite installConstraint];
expect(composite.childConstraints).to.haveCountOf(2);
expect(delegate.constraints).to.contain(composite.childConstraints[0]);
expect(delegate.constraints).to.contain(composite.childConstraints[1]);
});
it(@"should spawn child composite constraints", ^{
composite = [[MASCompositeConstraint alloc] initWithView:view type:MASCompositeConstraintTypeSize];
composite.delegate = delegate;
UIView *otherView = UIView.new;
[superview addSubview:otherView];
composite.lessThanOrEqualTo(@[@2, otherView]);
expect(composite.childConstraints).to.haveCountOf(2);
expect(composite.childConstraints[0]).to.beKindOf(MASCompositeConstraint.class);
expect(composite.childConstraints[1]).to.beKindOf(MASCompositeConstraint.class);
});
SpecEnd
\ No newline at end of file
... ...
... ... @@ -19,8 +19,8 @@
return self;
}
- (void)addConstraint:(id<MASConstraint>)constraint {
[self.constraints addObject:constraint];
- (void)constraint:(id<MASConstraint>)constraint shouldBeReplacedWithConstraint:(id<MASConstraint>)replacementConstraint {
[self.constraints replaceObjectAtIndex:[self.constraints indexOfObject:constraint] withObject:replacementConstraint];
}
@end
... ...
... ... @@ -10,6 +10,7 @@
#import "MASConstraint.h"
#import "UIView+MASAdditions.h"
#import "MASConstraintDelegateMock.h"
#import "MASCompositeConstraint.h"
@interface MASViewConstraint ()
... ... @@ -21,6 +22,12 @@
@end
@interface MASCompositeConstraint () <MASConstraintDelegate>
@property (nonatomic, strong) NSMutableArray *childConstraints;
@end
SpecBegin(MASViewConstraint)
__block MASConstraintDelegateMock *delegate;
... ... @@ -49,7 +56,6 @@ describe(@"create equality constraint", ^{
MASViewAttribute *secondViewAttribute = otherView.mas_top;
MASViewConstraint *newConstraint = (id)constraint.equalTo(secondViewAttribute);
expect(delegate.constraints).to.contain(constraint);
expect(newConstraint).to.beIdenticalTo(constraint);
expect(constraint.secondViewAttribute).to.beIdenticalTo(secondViewAttribute);
expect(constraint.layoutRelation).to.equal(NSLayoutRelationEqual);
... ... @@ -59,7 +65,6 @@ describe(@"create equality constraint", ^{
MASViewAttribute *secondViewAttribute = otherView.mas_top;
MASViewConstraint *newConstraint = (id)constraint.greaterThanOrEqualTo(secondViewAttribute);
expect(delegate.constraints).to.contain(constraint);
expect(newConstraint).to.beIdenticalTo(constraint);
expect(constraint.secondViewAttribute).to.beIdenticalTo(secondViewAttribute);
expect(constraint.layoutRelation).to.equal(NSLayoutRelationGreaterThanOrEqual);
... ... @@ -69,7 +74,6 @@ describe(@"create equality constraint", ^{
MASViewAttribute *secondViewAttribute = otherView.mas_top;
MASViewConstraint *newConstraint = (id)constraint.lessThanOrEqualTo(secondViewAttribute);
expect(delegate.constraints).to.contain(constraint);
expect(newConstraint).to.beIdenticalTo(constraint);
expect(constraint.secondViewAttribute).to.beIdenticalTo(secondViewAttribute);
expect(constraint.layoutRelation).to.equal(NSLayoutRelationLessThanOrEqual);
... ... @@ -112,11 +116,14 @@ describe(@"create equality constraint", ^{
it(@"should create composite when passed array of views", ^{
NSArray *views = @[UIView.new, UIView.new, UIView.new];
constraint.equalTo(views).priorityMedium().offset(-10);
[delegate.constraints addObject:constraint];
MASCompositeConstraint *composite = (id)constraint.equalTo(views).priorityMedium().offset(-10);
expect(delegate.constraints).to.haveCountOf(3);
for (MASViewConstraint *constraint in delegate.constraints) {
int index = [delegate.constraints indexOfObject:constraint];
expect(delegate.constraints).to.haveCountOf(1);
expect(delegate.constraints[0]).to.beKindOf(MASCompositeConstraint.class);
for (MASViewConstraint *constraint in composite.childConstraints) {
int index = [composite.childConstraints indexOfObject:constraint];
expect(constraint.secondViewAttribute.view).to.beIdenticalTo(views[index]);
expect(constraint.firstViewAttribute.layoutAttribute).to.equal(NSLayoutAttributeWidth);
expect(constraint.secondViewAttribute.layoutAttribute).to.equal(NSLayoutAttributeWidth);
... ... @@ -127,11 +134,14 @@ describe(@"create equality constraint", ^{
it(@"should create composite when passed array of attributes", ^{
NSArray *viewAttributes = @[UIView.new.mas_height, UIView.new.mas_left];
constraint.equalTo(viewAttributes).priority(60).offset(10);
[delegate.constraints addObject:constraint];
MASCompositeConstraint *composite = (id)constraint.equalTo(viewAttributes).priority(60).offset(10);
expect(delegate.constraints).to.haveCountOf(2);
for (MASViewConstraint *constraint in delegate.constraints) {
int index = [delegate.constraints indexOfObject:constraint];
expect(delegate.constraints).to.haveCountOf(1);
expect(delegate.constraints[0]).to.beKindOf(MASCompositeConstraint.class);
for (MASViewConstraint *constraint in composite.childConstraints) {
int index = [composite.childConstraints indexOfObject:constraint];
expect(constraint.secondViewAttribute.view).to.beIdenticalTo([viewAttributes[index] view]);
expect(constraint.firstViewAttribute.layoutAttribute).to.equal(NSLayoutAttributeWidth);
expect(constraint.secondViewAttribute.layoutAttribute).to.equal([viewAttributes[index] layoutAttribute]);
... ... @@ -144,7 +154,7 @@ describe(@"create equality constraint", ^{
describe(@"multiplier & constant", ^{
it(@"should not allow update of multiplier after layoutconstraint is created", ^{
[constraint commit];
[constraint installConstraint];
expect(^{
constraint.percent(0.9);
... ... @@ -152,7 +162,7 @@ describe(@"multiplier & constant", ^{
});
it(@"should allow update of constant after layoutconstraint is created", ^{
[constraint commit];
[constraint installConstraint];
constraint.offset(10);
expect(constraint.layoutConstant).to.equal(10);
... ... @@ -210,7 +220,7 @@ describe(@"multiplier & constant", ^{
});
});
describe(@"commit", ^{
describe(@"install", ^{
it(@"should create layout constraint on commit", ^{
MASViewAttribute *secondViewAttribute = otherView.mas_height;
... ... @@ -218,7 +228,7 @@ describe(@"commit", ^{
constraint.percent(0.5);
constraint.offset(10);
constraint.priority(345);
[constraint commit];
[constraint installConstraint];
expect(constraint.layoutConstraint.firstAttribute).to.equal(NSLayoutAttributeWidth);
expect(constraint.layoutConstraint.secondAttribute).to.equal(NSLayoutAttributeHeight);
... ... @@ -232,6 +242,18 @@ describe(@"commit", ^{
expect(superview.constraints[0]).to.beIdenticalTo(constraint.layoutConstraint);
});
it(@"should uninstall constraint", ^{
MASViewAttribute *secondViewAttribute = otherView.mas_height;
constraint.equalTo(secondViewAttribute);
[constraint installConstraint];
expect(superview.constraints).to.haveCountOf(1);
expect(superview.constraints[0]).to.equal(constraint.layoutConstraint);
[constraint uninstallConstraint];
expect(superview.constraints).to.haveCountOf(0);
});
});
SpecEnd
\ No newline at end of file
... ...