Browse Source

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

Jonas Budelmann 11 years ago
parent
commit
a7b077c75b

+ 2 - 0
Masonry/MASCompositeConstraint.m

@@ -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];
     }
 }

+ 5 - 0
Masonry/MASConstraint.h

@@ -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. 
  */

+ 5 - 0
Masonry/MASConstraintMaker.h

@@ -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
  *

+ 1 - 0
Masonry/MASConstraintMaker.m

@@ -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];

+ 33 - 3
Masonry/MASViewConstraint.m

@@ -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;

+ 11 - 0
Masonry/View+MASAdditions.h

@@ -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 - 0
Masonry/View+MASAdditions.m

@@ -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 {

+ 5 - 0
Masonry/View+MASShorthandAdditions.h

@@ -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

+ 6 - 0
MasonryExamples/Masonry iOS Examples.xcodeproj/project.pbxproj

@@ -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 */,

+ 3 - 0
MasonryExamples/Masonry iOS Examples/MASExampleListViewController.m

@@ -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;

+ 13 - 0
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 - 0
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 - 0
MasonryTests/MASConstraintMakerSpec.m

@@ -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);

+ 4 - 0
MasonryTests/MASViewAttributeSpec.m

@@ -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", ^{

+ 2 - 2
MasonryTests/NSLayoutConstraint+MASDebugAdditionsSpec.m

@@ -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);
 });
 

+ 9 - 0
MasonryTests/View+MASAdditionsSpec.m

@@ -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();