Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement layout margins relative arrangement. #27

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 36 additions & 2 deletions Example/OAStackView/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14E26a" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
Expand All @@ -18,6 +18,10 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aOB-5r-4uZ" userLabel="Background View">
<rect key="frame" x="16" y="28" width="272" height="128"/>
<color key="backgroundColor" red="0.94901960780000005" green="0.90588235289999997" blue="0.039215686270000001" alpha="1" colorSpace="calibratedRGB"/>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" placeholderIntrinsicWidth="177" placeholderIntrinsicHeight="128" translatesAutoresizingMaskIntoConstraints="NO" id="o1j-UM-Q0C" customClass="OAStackView">
<rect key="frame" x="16" y="28" width="272" height="128"/>
<subviews>
Expand Down Expand Up @@ -52,7 +56,6 @@
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.94901960780000005" green="0.90588235289999997" blue="0.039215686270000001" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="AwR-JP-fkI" firstAttribute="leading" secondItem="Ehd-QC-zJx" secondAttribute="trailing" constant="23" id="7Qi-07-WtC"/>
<constraint firstAttribute="width" constant="272" id="Q65-yu-Z4C"/>
Expand Down Expand Up @@ -221,6 +224,33 @@
<action selector="distributionEqualCentering:" destination="whP-gf-Uak" eventType="touchUpInside" id="WKO-AX-4ab"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="47g-89-34P">
<rect key="frame" x="249" y="238" width="79" height="30"/>
<state key="normal" title="No Margins">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="marginsTapped:" destination="whP-gf-Uak" eventType="touchUpInside" id="DuV-QU-yFb"/>
</connections>
</button>
<button opaque="NO" tag="100" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="h1s-to-MhG">
<rect key="frame" x="197" y="185" width="131" height="30"/>
<state key="normal" title="Margins (10,0,10,0)">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="marginsTapped:" destination="whP-gf-Uak" eventType="touchUpInside" id="G32-LY-9Bk"/>
</connections>
</button>
<button opaque="NO" tag="200" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ut8-pm-Xgm">
<rect key="frame" x="181" y="211" width="148" height="30"/>
<state key="normal" title="Margins (10,20,30,40)">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="marginsTapped:" destination="whP-gf-Uak" eventType="touchUpInside" id="j2w-o5-yjd"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
Expand Down Expand Up @@ -266,11 +296,15 @@
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="aOB-5r-4uZ" firstAttribute="centerY" secondItem="o1j-UM-Q0C" secondAttribute="centerY" id="0M5-p6-GWa"/>
<constraint firstAttribute="trailingMargin" secondItem="hOV-vh-kIh" secondAttribute="trailing" id="7k8-7m-x17"/>
<constraint firstItem="aOB-5r-4uZ" firstAttribute="centerX" secondItem="o1j-UM-Q0C" secondAttribute="centerX" id="8b5-ZU-7zD"/>
<constraint firstItem="hOV-vh-kIh" firstAttribute="top" secondItem="o1j-UM-Q0C" secondAttribute="bottom" constant="17" id="MjS-ee-sqP"/>
<constraint firstItem="o1j-UM-Q0C" firstAttribute="leading" secondItem="TpU-gO-2f1" secondAttribute="leadingMargin" id="MlC-bZ-a9a"/>
<constraint firstItem="o1j-UM-Q0C" firstAttribute="top" secondItem="uEw-UM-LJ8" secondAttribute="bottom" constant="8" id="OKI-dU-jvL"/>
<constraint firstItem="aOB-5r-4uZ" firstAttribute="width" secondItem="o1j-UM-Q0C" secondAttribute="width" id="V3Z-AR-HLg"/>
<constraint firstItem="hOV-vh-kIh" firstAttribute="leading" secondItem="TpU-gO-2f1" secondAttribute="leadingMargin" id="gZg-tv-ITI"/>
<constraint firstItem="aOB-5r-4uZ" firstAttribute="height" secondItem="o1j-UM-Q0C" secondAttribute="height" id="zH4-kr-eV3"/>
</constraints>
</view>
<simulatedScreenMetrics key="simulatedDestinationMetrics" type="retina47"/>
Expand Down
17 changes: 17 additions & 0 deletions Example/OAStackView/OAViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,21 @@ - (IBAction)distributionEqualCentering:(UIButton *)sender {
self.stackView.distribution = OAStackViewDistributionEqualCentering;
}

- (IBAction)marginsTapped:(UIButton *)sender {
switch (sender.tag) {
case 100:
self.stackView.layoutMarginsRelativeArrangement = YES;
self.stackView.layoutMargins = UIEdgeInsetsMake(10, 0, 10, 0);
break;
case 200:
self.stackView.layoutMarginsRelativeArrangement = YES;
self.stackView.layoutMargins = UIEdgeInsetsMake(10, 20, 30, 40);
break;
default:
self.stackView.layoutMarginsRelativeArrangement = NO;
self.stackView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
break;
}
}

@end
65 changes: 60 additions & 5 deletions Example/Tests/OAStackViewSpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,39 @@
});
});
});


context(@"Margins", ^{

__block UIView *view1, *view2, *view3;

beforeEach(^{
view1 = createView(100, 100);
view2 = createView(100, 100);
view3 = createView(100, 100);

NSArray *views = @[view1, view2, view3];

stackView = [[OAStackView alloc] initWithArrangedSubviews:views];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
stackView.layoutMarginsRelativeArrangement = YES;
stackView.layoutMargins = UIEdgeInsetsMake(10, 20, 30, 40);
});

it(@"Arranges the views relative to margins if set", ^{
layoutView(stackView);

[[theValue(view1.frame) should] equal:theValue(CGRectMake(20, 10, 100, 100))];
[[theValue(view2.frame) should] equal:theValue(CGRectMake(20, 110, 100, 100))];
[[theValue(view3.frame) should] equal:theValue(CGRectMake(20, 210, 100, 100))];
[[theValue(stackView.frame) should] equal:theValue(CGRectMake(0, 0, 160, 340))];
});
});

});


context(@"Horizontal", ^{

it(@"Can arrange views vertically", ^{
NSArray *views = @[createView(100, 40),createView(100, 40)];

Expand Down Expand Up @@ -946,8 +973,36 @@
});

});



context(@"Margins", ^{

__block UIView *view1, *view2, *view3;

beforeEach(^{
view1 = createView(100, 100);
view2 = createView(100, 100);
view3 = createView(100, 100);

NSArray *views = @[view1, view2, view3];

stackView = [[OAStackView alloc] initWithArrangedSubviews:views];
stackView.translatesAutoresizingMaskIntoConstraints = NO;
stackView.axis = UILayoutConstraintAxisHorizontal;
stackView.layoutMarginsRelativeArrangement = YES;
stackView.layoutMargins = UIEdgeInsetsMake(10, 20, 30, 40);
});

it(@"Arranges the views relative to margins if set", ^{
layoutView(stackView);

[[theValue(view1.frame) should] equal:theValue(CGRectMake(20, 10, 100, 100))];
[[theValue(view2.frame) should] equal:theValue(CGRectMake(120, 10, 100, 100))];
[[theValue(view3.frame) should] equal:theValue(CGRectMake(220, 10, 100, 100))];
[[theValue(stackView.frame) should] equal:theValue(CGRectMake(0, 0, 360, 140))];
});
});


context(@"bug fixes", ^{

__block UIView *view1, *view2, *view3;
Expand Down
3 changes: 3 additions & 0 deletions Pod/Classes/OAStackView.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic) OAStackViewDistribution distribution;
@property(nonatomic) IBInspectable NSInteger distributionValue;

@property(nonatomic) UIEdgeInsets layoutMargins;
@property(nonatomic, getter=isLayoutMarginsRelativeArrangement) BOOL layoutMarginsRelativeArrangement;

- (instancetype)initWithArrangedSubviews:(NSArray*)views NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

Expand Down
15 changes: 14 additions & 1 deletion Pod/Classes/OAStackView.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ - (void)commonInit {
_axis = UILayoutConstraintAxisVertical;
_alignment = OAStackViewAlignmentFill;
_distribution = OAStackViewDistributionFill;


_layoutMargins = UIEdgeInsetsMake(0, 8, 0, 8);
_layoutMarginsRelativeArrangement = NO;

_alignmentStrategy = [OAStackViewAlignmentStrategy strategyWithStackView:self];
_distributionStrategy = [OAStackViewDistributionStrategy strategyWithStackView:self];

Expand Down Expand Up @@ -151,6 +154,16 @@ - (void)setDistributionValue:(NSInteger)distributionValue {
self.distribution = distributionValue;
}

- (void)setLayoutMargins:(UIEdgeInsets)layoutMargins {
_layoutMargins = layoutMargins;
[self layoutArrangedViews];
}

- (void)setLayoutMarginsRelativeArrangement:(BOOL)layoutMarginsRelativeArrangement {
_layoutMarginsRelativeArrangement = layoutMarginsRelativeArrangement;
[self layoutArrangedViews];
}

#pragma mark Layouting

- (void)layoutSubviews {
Expand Down
97 changes: 60 additions & 37 deletions Pod/Classes/OAStackViewAlignmentStrategy.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ - (NSString*)otherAxisString {
return self.stackView.axis == UILayoutConstraintAxisHorizontal ? @"V" : @"H";
}

- (CGFloat)firstMargin {
if (self.stackView.axis == UILayoutConstraintAxisHorizontal) {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.top : 0.0f;
} else {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.left : 0.0f;
}
}

- (CGFloat)lastMargin {
if (self.stackView.axis == UILayoutConstraintAxisHorizontal) {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.bottom : 0.0f;
} else {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.right : 0.0f;
}
}

- (NSLayoutAttribute)centerAttribute {
return self.stackView.axis == UILayoutConstraintAxisHorizontal ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;
}
Expand All @@ -91,62 +107,69 @@ - (void)removeAddedConstraints {
[self.constraints removeAllObjects];
}

- (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view { /* subclassing */ return nil; }

@end
- (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view {
id constraintString = [NSString stringWithFormat:@"%@:|-(%@firstMargin)-[view]-(%@lastMargin)-|", [self otherAxisString], [self firstMarginRelation], [self lastMarginRelation]];

NSNumber *firstMargin = @([self firstMargin]);
NSNumber *lastMargin = @([self lastMargin]);
return [NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:NSDictionaryOfVariableBindings(firstMargin, lastMargin)
views:NSDictionaryOfVariableBindings(view)];
}

@implementation OAStackViewAlignmentStrategyFill
- (NSString *)firstMarginRelation {
return @"==";
}

- (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view {

id constraintString = [NSString stringWithFormat:@"%@:|-0-[view]-0-|", [self otherAxisString]];

return [NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)];
- (NSString *)lastMarginRelation {
return @"==";
}

@end

@implementation OAStackViewAlignmentStrategyLeading
@implementation OAStackViewAlignmentStrategyFill
@end

- (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view {

id constraintString = [NSString stringWithFormat:@"%@:|-0-[view]->=0-|", [self otherAxisString]];

return [NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)];
@implementation OAStackViewAlignmentStrategyLeading
- (NSString *)firstMarginRelation {
return @"==";
}

- (NSString *)lastMarginRelation {
return @">=";
}
@end

@implementation OAStackViewAlignmentStrategyTrailing

- (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view {

id constraintString = [NSString stringWithFormat:@"%@:|->=0-[view]-0-|", [self otherAxisString]];

return [NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)];
- (NSString *)firstMarginRelation {
return @">=";
}

- (NSString *)lastMarginRelation {
return @"==";
}
@end

@implementation OAStackViewAlignmentStrategyCenter
- (NSString *)firstMarginRelation {
return @">=";
}

- (NSString *)lastMarginRelation {
return @">=";
}

- (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view {

return @[[NSLayoutConstraint constraintWithItem:view
attribute:[self centerAttribute]
relatedBy:NSLayoutRelationEqual
toItem:view.superview
attribute:[self centerAttribute]
multiplier:1 constant:0]];
NSArray *constraints = [super constraintsalignViewOnOtherAxis:view];
CGFloat centerAdjustment = ([self firstMargin] - [self lastMargin]) / 2;
return [constraints arrayByAddingObject:[NSLayoutConstraint constraintWithItem:view
attribute:[self centerAttribute]
relatedBy:NSLayoutRelationEqual
toItem:view.superview
attribute:[self centerAttribute]
multiplier:1
constant:centerAdjustment]];
}

@end
26 changes: 22 additions & 4 deletions Pod/Classes/OAStackViewDistributionStrategy.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,22 @@ - (void)alignView:(UIView*)view afterView:(UIView*)previousView {
}

- (void)alignLastView:(UIView*)view {
NSString *constraintString = [NSString stringWithFormat:@"%@:[view]-0-|", [self currentAxisString]];
NSString *constraintString = [NSString stringWithFormat:@"%@:[view]-(lastMargin)-|", [self currentAxisString]];
NSNumber *lastMargin = @([self lastMargin]);
[self.stackView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:nil
metrics:NSDictionaryOfVariableBindings(lastMargin)
views:NSDictionaryOfVariableBindings(view)]];
}

- (void)alignFirstView:(UIView*)view {
NSString *str = [NSString stringWithFormat:@"%@:|-0-[view]", [self currentAxisString]];
NSString *str = [NSString stringWithFormat:@"%@:|-(firstMargin)-[view]", [self currentAxisString]];
NSNumber *firstMargin = @([self firstMargin]);
[self.stackView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:str
options:0
metrics:nil
metrics:NSDictionaryOfVariableBindings(firstMargin)
views:NSDictionaryOfVariableBindings(view)]];
}

Expand Down Expand Up @@ -130,6 +132,22 @@ - (NSLayoutAttribute)equalityAxis {
return self.stackView.axis == UILayoutConstraintAxisVertical ? NSLayoutAttributeHeight : NSLayoutAttributeWidth;
}

- (CGFloat)firstMargin {
if (self.stackView.axis == UILayoutConstraintAxisHorizontal) {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.left : 0.0f;
} else {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.top : 0.0f;
}
}

- (CGFloat)lastMargin {
if (self.stackView.axis == UILayoutConstraintAxisHorizontal) {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.right : 0.0f;
} else {
return self.stackView.layoutMarginsRelativeArrangement ? self.stackView.layoutMargins.bottom : 0.0f;
}
}

- (NSMutableArray *)constraints {
if (!_constraints) {
_constraints = [@[] mutableCopy];
Expand Down