Skip to content

Commit

Permalink
Merge branch 'auto_update' of https://github.com/cloudkite/Masonry
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudkite committed Nov 3, 2013
2 parents 61a7a00 + 19a7e72 commit a7b077c
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 5 deletions.
2 changes: 2 additions & 0 deletions Masonry/MASCompositeConstraint.m
Expand Up @@ -18,6 +18,7 @@ @interface MASCompositeConstraint () <MASConstraintDelegate>
@implementation MASCompositeConstraint

@synthesize delegate = _delegate;
@synthesize updateExisting = _updateExisting;

- (id)initWithChildren:(NSArray *)children {
self = [super init];
Expand Down Expand Up @@ -181,6 +182,7 @@ - (void)constraint:(id<MASConstraint>)constraint shouldBeReplacedWithConstraint:

- (void)install {
for (id<MASConstraint> constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
Expand Down
5 changes: 5 additions & 0 deletions Masonry/MASConstraint.h
Expand Up @@ -113,6 +113,11 @@
*/
@property (nonatomic, copy, readonly) id<MASConstraint> (^key)(id key);

/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;

/**
* Creates a NSLayoutConstraint. The constraint is installed to the first view or the or the closest common superview of the first and second view.
*/
Expand Down
5 changes: 5 additions & 0 deletions Masonry/MASConstraintMaker.h
Expand Up @@ -53,6 +53,11 @@
*/
@property (nonatomic, strong, readonly) id<MASConstraint> center;

/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;

/**
* initialises the maker with a default view
*
Expand Down
1 change: 1 addition & 0 deletions Masonry/MASConstraintMaker.m
Expand Up @@ -34,6 +34,7 @@ - (id)initWithView:(MAS_VIEW *)view {
- (NSArray *)install {
NSArray *constraints = self.constraints.copy;
for (id<MASConstraint> constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
Expand Down
36 changes: 33 additions & 3 deletions Masonry/MASViewConstraint.m
Expand Up @@ -28,6 +28,7 @@ @interface MASViewConstraint ()
@implementation MASViewConstraint

@synthesize delegate = _delegate;
@synthesize updateExisting = _updateExisting;

- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
Expand Down Expand Up @@ -291,15 +292,44 @@ - (void)install {
@"couldn't find a common superview for %@ and %@",
firstLayoutItem, secondLayoutItem);
self.installedView = closestCommonSuperview;
[closestCommonSuperview addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
} else {
self.installedView = firstLayoutItem;
[firstLayoutItem addConstraint:layoutConstraint];
}


MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimiliarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
}
}

- (MASLayoutConstraint *)layoutConstraintSimiliarTo:(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.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;

return (id)existingConstraint;
}
return nil;
}

- (void)uninstall {
[self.installedView removeConstraint:self.layoutConstraint];
self.layoutConstraint = nil;
Expand Down
11 changes: 11 additions & 0 deletions Masonry/View+MASAdditions.h
Expand Up @@ -55,4 +55,15 @@
*/
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;

/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;

@end
8 changes: 8 additions & 0 deletions Masonry/View+MASAdditions.m
Expand Up @@ -18,6 +18,14 @@ - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
return [constraintMaker install];
}

- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}

#pragma mark - NSLayoutAttribute properties

- (MASViewAttribute *)mas_left {
Expand Down
5 changes: 5 additions & 0 deletions Masonry/View+MASShorthandAdditions.h
Expand Up @@ -29,6 +29,7 @@
@property (nonatomic, strong, readonly) MASViewAttribute *baseline;

- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;

@end

Expand All @@ -55,6 +56,10 @@ MAS_ATTR_FORWARD(baseline);
return [self mas_makeConstraints:block];
}

- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}

@end

#endif
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
DD175E6A182639FB0099129A /* MASExampleUpdateView.m in Sources */ = {isa = PBXBuildFile; fileRef = DD175E69182639FB0099129A /* MASExampleUpdateView.m */; };
DD52F22B179CAD57005CD195 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD52F22A179CAD57005CD195 /* UIKit.framework */; };
DD52F22D179CAD57005CD195 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD52F22C179CAD57005CD195 /* Foundation.framework */; };
DD52F22F179CAD57005CD195 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD52F22E179CAD57005CD195 /* CoreGraphics.framework */; };
Expand All @@ -31,6 +32,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
DD175E68182639FB0099129A /* MASExampleUpdateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MASExampleUpdateView.h; sourceTree = "<group>"; };
DD175E69182639FB0099129A /* MASExampleUpdateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MASExampleUpdateView.m; sourceTree = "<group>"; };
DD52F227179CAD57005CD195 /* Masonry iOS Examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Masonry iOS Examples.app"; sourceTree = BUILT_PRODUCTS_DIR; };
DD52F22A179CAD57005CD195 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
DD52F22C179CAD57005CD195 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -157,6 +160,8 @@
DD7CC17517ACE990007A469E /* MASExampleDebuggingView.m */,
DDDF60CA181915E300BF7B8B /* MASExampleLabelView.h */,
DDDF60CB181915E300BF7B8B /* MASExampleLabelView.m */,
DD175E68182639FB0099129A /* MASExampleUpdateView.h */,
DD175E69182639FB0099129A /* MASExampleUpdateView.m */,
);
name = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -261,6 +266,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DD175E6A182639FB0099129A /* MASExampleUpdateView.m in Sources */,
DD52F237179CAD57005CD195 /* main.m in Sources */,
DD52F23B179CAD57005CD195 /* MASAppDelegate.m in Sources */,
DD52F251179CADC0005CD195 /* MASExampleBasicView.m in Sources */,
Expand Down
Expand Up @@ -14,6 +14,7 @@
#import "MASExampleAnimatedView.h"
#import "MASExampleDebuggingView.h"
#import "MASExampleLabelView.h"
#import "MASExampleUpdateView.h"

static NSString * const kMASCellReuseIdentifier = @"kMASCellReuseIdentifier";

Expand Down Expand Up @@ -44,6 +45,8 @@ - (id)init {
viewClass:MASExampleDebuggingView.class],
[[MASExampleViewController alloc] initWithTitle:@"Bacony Labels"
viewClass:MASExampleLabelView.class],
[[MASExampleViewController alloc] initWithTitle:@"Update constraints"
viewClass:MASExampleUpdateView.class],
];

return self;
Expand Down
13 changes: 13 additions & 0 deletions MasonryExamples/Masonry iOS Examples/MASExampleUpdateView.h
@@ -0,0 +1,13 @@
//
// MASExampleUpdateView.h
// Masonry iOS Examples
//
// Created by Jonas Budelmann on 3/11/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface MASExampleUpdateView : UIView

@end
59 changes: 59 additions & 0 deletions MasonryExamples/Masonry iOS Examples/MASExampleUpdateView.m
@@ -0,0 +1,59 @@
//
// MASExampleUpdateView.m
// Masonry iOS Examples
//
// Created by Jonas Budelmann on 3/11/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//

#import "MASExampleUpdateView.h"

@interface MASExampleUpdateView ()

@property (nonatomic, strong) UIButton *growingButton;
@property (nonatomic, assign) CGSize buttonSize;

@end

@implementation MASExampleUpdateView

- (id)init {
self = [super init];
if (!self) return nil;

self.growingButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.growingButton setTitle:@"Grow Me!" forState:UIControlStateNormal];
self.growingButton.layer.borderColor = UIColor.greenColor.CGColor;
self.growingButton.layer.borderWidth = 2;

[self.growingButton addTarget:self action:@selector(didTapGrowButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.growingButton];

self.buttonSize = CGSizeMake(100, 100);
[self setNeedsUpdateConstraints];

return self;
}

- (void)updateConstraints {
[super updateConstraints];

[self.growingButton updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
}

- (void)didTapGrowButton:(UIButton *)button {
self.buttonSize = CGSizeMake(self.buttonSize.width * 1.1, self.buttonSize.height * 1.1);

[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];

[UIView animateWithDuration:0.4 animations:^{
[self layoutIfNeeded];
}];
}

@end
41 changes: 41 additions & 0 deletions MasonryTests/MASConstraintMakerSpec.m
Expand Up @@ -104,6 +104,47 @@ @interface MASCompositeConstraint ()
expect([maker install]).to.haveCountOf(2);
});

it(@"should update constraints", ^{
MAS_VIEW *newView = MAS_VIEW.new;
[superview addSubview:newView];

maker.updateExisting = YES;
maker.left.equalTo(newView).offset(10);
[maker install];

NSLayoutConstraint *constraint1 = superview.constraints[0];
expect(constraint1.constant).to.equal(10);

maker.left.equalTo(newView).offset(20);
[maker install];

expect(superview.constraints).to.haveCountOf(1);
NSLayoutConstraint *constraint2 = superview.constraints[0];
expect(constraint2.constant).to.equal(20);

expect(constraint2).to.beIdenticalTo(constraint2);
});

it(@"should not update constraint", ^{
MAS_VIEW *newView = MAS_VIEW.new;
[superview addSubview:newView];

maker.updateExisting = YES;
maker.left.equalTo(newView).offset(10);
[maker install];

NSLayoutConstraint *constraint1 = superview.constraints[0];
expect(constraint1.constant).to.equal(10);

maker.right.equalTo(newView).offset(20);
[maker install];

expect(superview.constraints).to.haveCountOf(2);
NSLayoutConstraint *constraint2 = superview.constraints[1];
expect(constraint1.constant).to.equal(10);
expect(constraint2.constant).to.equal(20);
});

it(@"should create new constraints", ^{
expect(maker.left).notTo.beIdenticalTo(maker.left);
expect(maker.right).notTo.beIdenticalTo(maker.right);
Expand Down
4 changes: 4 additions & 0 deletions MasonryTests/MASViewAttributeSpec.m
Expand Up @@ -45,6 +45,10 @@
layoutAttribute:NSLayoutAttributeRight];
expect([viewAttribute isEqual:otherViewAttribute]).to.equal(NO);
});

it(@"should return NO when non view attribute passed", ^{
expect([viewAttribute isEqual:NSArray.new]).to.equal(NO);
});
});

context(@"hash", ^{
Expand Down
4 changes: 2 additions & 2 deletions MasonryTests/NSLayoutConstraint+MASDebugAdditionsSpec.m
Expand Up @@ -32,10 +32,10 @@
MAS_VIEW *newView2 = MAS_VIEW.new;
newView2.mas_key = @"newView2";

MASLayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:newView1 attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:newView2 attribute:NSLayoutAttributeTop multiplier:1 constant:300];
MASLayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:newView1 attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:newView2 attribute:NSLayoutAttributeTop multiplier:2 constant:300];
layoutConstraint.mas_key = @"helloConstraint";

NSString *description = [NSString stringWithFormat:@"<MASLayoutConstraint:helloConstraint %@:newView1.baseline == %@:newView2.top + 300>", MAS_VIEW.class, MAS_VIEW.class];
NSString *description = [NSString stringWithFormat:@"<MASLayoutConstraint:helloConstraint %@:newView1.baseline == %@:newView2.top * 2 + 300>", MAS_VIEW.class, MAS_VIEW.class];
expect([layoutConstraint description]).to.equal(description);
});

Expand Down
9 changes: 9 additions & 0 deletions MasonryTests/View+MASAdditionsSpec.m
Expand Up @@ -13,7 +13,16 @@
it(@"should set translatesAutoresizingMaskIntoConstraints", ^{
MAS_VIEW *newView = MAS_VIEW.new;
[newView mas_makeConstraints:^(MASConstraintMaker *make) {
expect(make.updateExisting).to.beFalsy();
}];

expect(newView.translatesAutoresizingMaskIntoConstraints).to.beFalsy();
});

it(@"should set updateExisting", ^{
MAS_VIEW *newView = MAS_VIEW.new;
[newView mas_updateConstraints:^(MASConstraintMaker *make) {
expect(make.updateExisting).to.beTruthy();
}];

expect(newView.translatesAutoresizingMaskIntoConstraints).to.beFalsy();
Expand Down

0 comments on commit a7b077c

Please sign in to comment.