Authored by Jonas Budelmann

Merge branch 'auto_update' of https://github.com/cloudkite/Masonry

... ... @@ -18,6 +18,7 @@
@implementation MASCompositeConstraint
@synthesize delegate = _delegate;
@synthesize updateExisting = _updateExisting;
- (id)initWithChildren:(NSArray *)children {
self = [super init];
... ... @@ -181,6 +182,7 @@
- (void)install {
for (id<MASConstraint> constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
... ...
... ... @@ -114,6 +114,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.
*/
- (void)install;
... ...
... ... @@ -54,6 +54,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
*
* @param view any MASConstrait are created with this view as the first item
... ...
... ... @@ -34,6 +34,7 @@
- (NSArray *)install {
NSArray *constraints = self.constraints.copy;
for (id<MASConstraint> constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
... ...
... ... @@ -28,6 +28,7 @@
@implementation MASViewConstraint
@synthesize delegate = _delegate;
@synthesize updateExisting = _updateExisting;
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
... ... @@ -291,15 +292,44 @@
@"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;
... ...
... ... @@ -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
... ...
... ... @@ -18,6 +18,14 @@
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 {
... ...
... ... @@ -29,6 +29,7 @@
@property (nonatomic, strong, readonly) MASViewAttribute *baseline;
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
@end
... ... @@ -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
... ...
... ... @@ -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 */; };
... ... @@ -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; };
... ... @@ -157,6 +160,8 @@
DD7CC17517ACE990007A469E /* MASExampleDebuggingView.m */,
DDDF60CA181915E300BF7B8B /* MASExampleLabelView.h */,
DDDF60CB181915E300BF7B8B /* MASExampleLabelView.m */,
DD175E68182639FB0099129A /* MASExampleUpdateView.h */,
DD175E69182639FB0099129A /* MASExampleUpdateView.m */,
);
name = Views;
sourceTree = "<group>";
... ... @@ -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 */,
... ...
... ... @@ -14,6 +14,7 @@
#import "MASExampleAnimatedView.h"
#import "MASExampleDebuggingView.h"
#import "MASExampleLabelView.h"
#import "MASExampleUpdateView.h"
static NSString * const kMASCellReuseIdentifier = @"kMASCellReuseIdentifier";
... ... @@ -44,6 +45,8 @@ static NSString * const kMASCellReuseIdentifier = @"kMASCellReuseIdentifier";
viewClass:MASExampleDebuggingView.class],
[[MASExampleViewController alloc] initWithTitle:@"Bacony Labels"
viewClass:MASExampleLabelView.class],
[[MASExampleViewController alloc] initWithTitle:@"Update constraints"
viewClass:MASExampleUpdateView.class],
];
return self;
... ...
//
// 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
... ...
//
// 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
... ...
... ... @@ -104,6 +104,47 @@ it(@"should install constraints", ^{
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);
... ...
... ... @@ -45,6 +45,10 @@ context(@"isEqual", ^{
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", ^{
... ...
... ... @@ -32,10 +32,10 @@ it(@"should display layoutConstraint key", ^{
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);
});
... ...
... ... @@ -13,7 +13,16 @@ SpecBegin(View_MASAdditions)
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();
... ...