From f2fb0ff1720ca02323b0968b8ca0ff1b48c41407 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Thu, 25 Jun 2015 14:58:01 +0200 Subject: [PATCH 01/10] added convenience initializer without parameters --- Pod/Classes/OAStackView.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index ed2aa82..1355ad8 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -38,6 +38,14 @@ - (instancetype)initWithCoder:(NSCoder *)coder { return self; } +- (instancetype)init { + self = [self initWithArrangedSubviews:@[]]; + if (self) { + + } + return self; +} + - (instancetype)initWithArrangedSubviews:(NSArray*)views { self = [super initWithFrame:CGRectZero]; From 3f58ef3d7174e9cbc54c36ffeefa36ee4a103408 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Thu, 25 Jun 2015 15:03:06 +0200 Subject: [PATCH 02/10] Refactored the way arrangedSubviews are handled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - removing a view from the arranged subviews now does not remove it as a subview (like UIStackView) - this needed a strong separation between subviews and arrangedSubviews, not just having arrangedSubviews be all non-hidden subviews - implemented the arrangedSubviews @property, which was previously unused - arrangedSubviews is now KVO’able --- Example/Pods/Pods.xcodeproj/project.pbxproj | 6 + Pod/Classes/OAStackView+Hiding.m | 12 -- Pod/Classes/OAStackView+Traversal.h | 12 +- Pod/Classes/OAStackView+Traversal.m | 58 ++---- Pod/Classes/OAStackView.h | 2 + Pod/Classes/OAStackView.m | 192 ++++++++++++-------- Pod/Classes/OAStackView_ArrangedSubviews.h | 27 +++ 7 files changed, 176 insertions(+), 133 deletions(-) create mode 100644 Pod/Classes/OAStackView_ArrangedSubviews.h diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj index 6c50acd..6ac1ce0 100644 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -206,6 +206,8 @@ E91726963A1D70D65A0C6428 /* KWRespondToSelectorMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA8AD367C971DD82427F4AE /* KWRespondToSelectorMatcher.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; E943786F62DF61D7A6454356 /* KWBeMemberOfClassMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EFCED26B5D9A256C2B1F238 /* KWBeMemberOfClassMatcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; E961F1E03FB1C2349361DD43 /* KWProbe.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ED94E0B9B0394509B3E09C5 /* KWProbe.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E98AFA0C1B3C336D00CDF427 /* OAStackView_ArrangedSubviews.h in Headers */ = {isa = PBXBuildFile; fileRef = E98AFA0B1B3C336D00CDF427 /* OAStackView_ArrangedSubviews.h */; }; + E98AFA0E1B3C337400CDF427 /* OAStackView_ArrangedSubviews.h in Headers */ = {isa = PBXBuildFile; fileRef = E98AFA0B1B3C336D00CDF427 /* OAStackView_ArrangedSubviews.h */; }; EB6342DDFC16EA1697673367 /* NSInvocation+OCMAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 87EF738D1C4A43D46DF7218C /* NSInvocation+OCMAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; EBA3CB1AFC1C14BE0CE682AD /* KWRegisterMatchersNode.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC8D3E191EA0619F7399012 /* KWRegisterMatchersNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; EBD4862464FD997A831166EA /* KWSuiteConfigurationBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 30DA7C8C1EFC416457FC8B42 /* KWSuiteConfigurationBase.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -498,6 +500,7 @@ E91B86F74BD64D5D3108D23A /* Pods-OAStackView_Example-OAStackView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-OAStackView_Example-OAStackView-umbrella.h"; sourceTree = ""; }; E96052DEDBABAD6DE67AB9CC /* KWVerifying.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = KWVerifying.h; path = Classes/Verifiers/KWVerifying.h; sourceTree = ""; }; E9763017ABDD60733231C7B9 /* KWBeforeEachNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = KWBeforeEachNode.m; path = Classes/Nodes/KWBeforeEachNode.m; sourceTree = ""; }; + E98AFA0B1B3C336D00CDF427 /* OAStackView_ArrangedSubviews.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OAStackView_ArrangedSubviews.h; sourceTree = ""; }; E9DCF701A29E982F7F76150C /* Pods-OAStackView_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-OAStackView_Example.release.xcconfig"; sourceTree = ""; }; EC4FD93B76378B54BA02E9D3 /* KWGenericMatchEvaluator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = KWGenericMatchEvaluator.m; path = Classes/Matchers/KWGenericMatchEvaluator.m; sourceTree = ""; }; EE003196DB6E4BE2B7583341 /* Pods-OAStackView_Tests-OAStackView.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OAStackView_Tests-OAStackView.xcconfig"; path = "../Pods-OAStackView_Tests-OAStackView/Pods-OAStackView_Tests-OAStackView.xcconfig"; sourceTree = ""; }; @@ -586,6 +589,7 @@ children = ( 8B90E6BD21591A4585962496 /* OAStackView.h */, 5D2F4F1397A17E203EA8112B /* OAStackView.m */, + E98AFA0B1B3C336D00CDF427 /* OAStackView_ArrangedSubviews.h */, C809B772AB6D8B7D66C7D8A2 /* OAStackView+Constraint.h */, E822B703CD09D5556B8F8265 /* OAStackView+Constraint.m */, E86334892F73798F2B097D5F /* OAStackView+Hiding.h */, @@ -1067,6 +1071,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E98AFA0E1B3C337400CDF427 /* OAStackView_ArrangedSubviews.h in Headers */, D69542A762933D4119F4037C /* OAStackView+Constraint.h in Headers */, 05794B5F4247E3A648C40B51 /* OAStackView+Hiding.h in Headers */, B1868D729956D7DEC9C9FE06 /* OAStackView+Traversal.h in Headers */, @@ -1089,6 +1094,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E98AFA0C1B3C336D00CDF427 /* OAStackView_ArrangedSubviews.h in Headers */, 06EC4010B3D981437FC3D92B /* OAStackView+Constraint.h in Headers */, 19308B2A9CB35F4AD26FD0CF /* OAStackView+Hiding.h in Headers */, 46CA8779A92EBC2D28E4C1EC /* OAStackView+Traversal.h in Headers */, diff --git a/Pod/Classes/OAStackView+Hiding.m b/Pod/Classes/OAStackView+Hiding.m index 6f4774f..73687ee 100644 --- a/Pod/Classes/OAStackView+Hiding.m +++ b/Pod/Classes/OAStackView+Hiding.m @@ -51,16 +51,4 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N } -#pragma mark subviews - -- (void)didAddSubview:(UIView *)subview { - [super didAddSubview:subview]; - [self addObserverForView:subview]; -} - -- (void)willRemoveSubview:(UIView *)subview { - [super willRemoveSubview:subview]; - [self removeObserverForView:subview]; -} - @end diff --git a/Pod/Classes/OAStackView+Traversal.h b/Pod/Classes/OAStackView+Traversal.h index 92c4007..0f6610c 100644 --- a/Pod/Classes/OAStackView+Traversal.h +++ b/Pod/Classes/OAStackView+Traversal.h @@ -10,15 +10,13 @@ @interface OAStackView (Traversal) -- (UIView*)visibleViewBeforeIndex:(NSInteger)index; -- (UIView*)visibleViewBeforeView:(UIView*)view; +- (UIView*)arrangedSubviewBeforeIndex:(NSInteger)index; +- (UIView*)arrangedSubviewBeforeView:(UIView*)view; -- (UIView*)visibleViewAfterIndex:(NSInteger)index; -- (UIView*)visibleViewAfterView:(UIView*)view; +- (UIView*)arrangedSubviewAfterIndex:(NSInteger)index; +- (UIView*)arrangedSubviewAfterView:(UIView*)view; -- (void)iterateVisibleViews:(void (^) (UIView *view, UIView *previousView))block; - -- (UIView*)lastVisibleItem; +- (void)iterateArrangedSubviews:(void (^) (UIView *view, UIView *previousView))block; - (NSLayoutConstraint*)firstViewConstraint; - (NSLayoutConstraint*)lastViewConstraint; diff --git a/Pod/Classes/OAStackView+Traversal.m b/Pod/Classes/OAStackView+Traversal.m index 234606b..e56d1d3 100644 --- a/Pod/Classes/OAStackView+Traversal.m +++ b/Pod/Classes/OAStackView+Traversal.m @@ -7,72 +7,50 @@ // #import "OAStackView+Traversal.h" +#import "OAStackView_ArrangedSubviews.h" @implementation OAStackView (Traversal) -- (UIView*)visibleViewBeforeView:(UIView*)view { - NSInteger index = [self.subviews indexOfObject:view]; +- (UIView*)arrangedSubviewBeforeView:(UIView*)view { + NSInteger index = [self indexInArrangedSubviewsOfObject:view]; if (index == NSNotFound) { return nil; } - return [self visibleViewBeforeIndex:index]; + return [self arrangedSubviewBeforeIndex:index]; } -- (UIView*)visibleViewAfterView:(UIView*)view { - NSInteger index = [self.subviews indexOfObject:view]; +- (UIView*)arrangedSubviewAfterView:(UIView*)view { + NSInteger index = [self indexInArrangedSubviewsOfObject:view]; if (index == NSNotFound) { return nil; } - return [self visibleViewAfterIndex:index]; + return [self arrangedSubviewAfterIndex:index]; } -- (UIView*)visibleViewAfterIndex:(NSInteger)index { - for (NSInteger i = index + 1; i < self.subviews.count; i++) { - UIView *theView = self.subviews[i]; - if (!theView.hidden) { - return theView; - } +- (UIView*)arrangedSubviewAfterIndex:(NSInteger)index { + if ((index + 1) < [self countOfArrangedSubviews]) { + return [self objectInArrangedSubviewsAtIndex:index+1]; } return nil; } -- (UIView*)visibleViewBeforeIndex:(NSInteger)index { - for (NSInteger i = index - 1; i >= 0; i--) { - UIView *theView = self.subviews[i]; - if (!theView.hidden) { - return theView; - } +- (UIView*)arrangedSubviewBeforeIndex:(NSInteger)index { + if (index > 0) { + return [self objectInArrangedSubviewsAtIndex:index-1]; } return nil; } -- (UIView*)lastVisibleItem { - return [self visibleViewBeforeIndex:self.subviews.count]; -} - -- (void)iterateVisibleViews:(void (^) (UIView *view, UIView *previousView))block { - - id previousView; - for (UIView *view in self.subviews) { - if (view.isHidden) { continue; } +- (void)iterateArrangedSubviews:(void (^) (UIView *view, UIView *previousView))block { + UIView *previousView = nil; + for (NSUInteger i = 0; i < [self countOfArrangedSubviews]; i++) { + UIView *view = [self objectInArrangedSubviewsAtIndex:i]; block(view, previousView); previousView = view; } } -- (NSArray*)currentVisibleViews { - NSMutableArray *arr = [@[] mutableCopy]; - [self iterateVisibleViews:^(UIView *view, UIView *previousView) { - [arr addObject:view]; - }]; - return arr; -} - -- (BOOL)isLastVisibleItem:(UIView*)view { - return view == [self lastVisibleItem]; -} - - (NSLayoutConstraint*)lastViewConstraint { for (NSLayoutConstraint *constraint in self.constraints) { @@ -113,7 +91,7 @@ - (NSLayoutConstraint*)firstViewConstraint { } - (BOOL)isViewLastItem:(UIView*)view excludingItem:(UIView*)excludingItem { - NSArray *visible = [self currentVisibleViews]; + NSArray *visible = [self arrangedSubviews]; NSInteger index = [visible indexOfObject:view]; NSInteger exclutedIndex = [visible indexOfObject:excludingItem]; diff --git a/Pod/Classes/OAStackView.h b/Pod/Classes/OAStackView.h index 4a358d6..8b8931a 100644 --- a/Pod/Classes/OAStackView.h +++ b/Pod/Classes/OAStackView.h @@ -83,6 +83,8 @@ typedef NS_ENUM(NSInteger, OAStackViewAlignment) { NS_ASSUME_NONNULL_BEGIN @interface OAStackView : UIView +// The views that are currently being arranged. +// Views that are hidden will be removed from this list @property(nonatomic,readonly,copy) NSArray *arrangedSubviews; //Default is Vertical diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index 1355ad8..40e9d15 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -7,6 +7,7 @@ // #import "OAStackView.h" +#import "OAStackView_ArrangedSubviews.h" #import "OAStackView+Constraint.h" #import "OAStackView+Hiding.h" #import "OAStackView+Traversal.h" @@ -14,7 +15,8 @@ #import "OAStackViewDistributionStrategy.h" @interface OAStackView () -@property(nonatomic, copy) NSArray *arrangedSubviews; + +@property (nonatomic) NSMutableArray *mutableArrangedSubviews; @property(nonatomic) OAStackViewAlignmentStrategy *alignmentStrategy; @property(nonatomic) OAStackViewDistributionStrategy *distributionStrategy; @@ -33,6 +35,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { if (self) { [self commonInit]; + [self addAllSubviewsAsArrangedSubviews]; } return self; @@ -50,8 +53,8 @@ - (instancetype)initWithArrangedSubviews:(NSArray*)views { self = [super initWithFrame:CGRectZero]; if (self) { - [self addViewsAsSubviews:views]; [self commonInit]; + [self addArrangedSubviews:views]; } return self; @@ -62,6 +65,7 @@ - (instancetype)initWithFrame:(CGRect)frame { } - (void)commonInit { + _mutableArrangedSubviews = [NSMutableArray new]; _axis = UILayoutConstraintAxisVertical; _alignment = OAStackViewAlignmentFill; _distribution = OAStackViewDistributionFill; @@ -72,6 +76,12 @@ - (void)commonInit { [self layoutArrangedViews]; } +- (void)addAllSubviewsAsArrangedSubviews { + for (UIView *view in self.subviews) { + [self addArrangedSubview:view]; + } +} + #pragma mark - Properties - (void)setBackgroundColor:(UIColor *)backgroundColor { @@ -92,8 +102,8 @@ - (void)setSpacing:(CGFloat)spacing { (constraint.firstAttribute == NSLayoutAttributeWidth) || (constraint.firstAttribute == NSLayoutAttributeHeight); - if ([self.subviews containsObject:constraint.firstItem] && - [self.subviews containsObject:constraint.secondItem] && + if ([_mutableArrangedSubviews containsObject:constraint.firstItem] && + [_mutableArrangedSubviews containsObject:constraint.secondItem] && !isWidthOrHeight) { constraint.constant = spacing; } @@ -122,7 +132,7 @@ - (void)setAlignment:(OAStackViewAlignment)alignment { [self.alignmentStrategy removeAddedConstraints]; self.alignmentStrategy = [OAStackViewAlignmentStrategy strategyWithStackView:self]; - [self iterateVisibleViews:^(UIView *view, UIView *previousView) { + [self iterateArrangedSubviews:^(UIView *view, UIView *previousView) { [self.alignmentStrategy addConstraintsOnOtherAxis:view]; }]; } @@ -143,7 +153,7 @@ - (void)setDistribution:(OAStackViewDistribution)distribution { self.alignmentStrategy = [OAStackViewAlignmentStrategy strategyWithStackView:self]; self.distributionStrategy = [OAStackViewDistributionStrategy strategyWithStackView:self]; - [self iterateVisibleViews:^(UIView *view, UIView *previousView) { + [self iterateArrangedSubviews:^(UIView *view, UIView *previousView) { [self.alignmentStrategy addConstraintsOnOtherAxis:view]; [self.distributionStrategy alignView:view afterView:previousView]; }]; @@ -166,7 +176,7 @@ - (CGSize)intrinsicContentSize { __block float maxSize = 0; - [self iterateVisibleViews:^(UIView *view, UIView *previousView) { + [self iterateArrangedSubviews:^(UIView *view, UIView *previousView) { if (self.axis == UILayoutConstraintAxisVertical) { maxSize = fmaxf(maxSize, CGRectGetWidth(view.frame)); } else { @@ -185,69 +195,55 @@ - (CGSize)intrinsicContentSize { #pragma mark - Adding and removing +- (void)addArrangedSubviews:(NSArray *)views +{ + for (UIView *view in views) { + [self addArrangedSubview:view]; + } +} + - (void)addArrangedSubview:(UIView *)view { - [self insertArrangedSubview:view atIndex:self.subviews.count]; + [self insertArrangedSubview:view atIndex:[self countOfArrangedSubviews]]; } -- (void)removeArrangedSubview:(UIView *)view { - - if (self.subviews.count == 1) { - [view removeFromSuperview]; - return; - } - - [self removeViewFromArrangedViews:view permanently:YES]; +- (void)insertArrangedSubview:(UIView * __nonnull)view atIndex:(NSUInteger)stackIndex { + [self insertObject:view inArrangedSubviewsAtIndex:stackIndex]; } -- (void)insertArrangedSubview:(UIView *)view atIndex:(NSUInteger)stackIndex { - [self insertArrangedSubview:view atIndex:stackIndex newItem:YES]; +- (void)removeArrangedSubview:(UIView *)view { + NSUInteger index = [_mutableArrangedSubviews indexOfObject:view]; + if (index != NSNotFound) { + [self removeObjectFromArrangedSubviewsAtIndex:index]; + } } -- (void)insertArrangedSubview:(UIView *)view atIndex:(NSUInteger)stackIndex newItem:(BOOL)newItem { +- (void)didAddArrangedSubview:(UIView *)view { + NSAssert([_mutableArrangedSubviews containsObject:view] && [self.subviews containsObject:view], @"View should already be added as subview and arranged view"); + NSUInteger stackIndex = [_mutableArrangedSubviews indexOfObject:view]; - id previousView, nextView; + UIView *previousView = [self arrangedSubviewBeforeIndex:stackIndex]; + UIView *nextView = [self arrangedSubviewAfterIndex:stackIndex]; view.translatesAutoresizingMaskIntoConstraints = NO; - BOOL isAppending = stackIndex == self.subviews.count; - if (isAppending) { + if (stackIndex == [self countOfArrangedSubviews]-1) { //Appending a new item - - previousView = [self lastVisibleItem]; - nextView = nil; - + NSArray *constraints = [self lastConstraintAffectingView:self andView:previousView inAxis:self.axis]; [self removeConstraints:constraints]; - if (newItem) { - [self addSubview:view]; + if ([self firstArrangedSubview] == previousView) { + [self.distributionStrategy alignView:previousView afterView:nil]; } - + } else if (stackIndex == 0) { + // Prepending a new item + NSArray *constraints = [self firstConstraintAffectingView:self andView:nextView inAxis:self.axis]; + [self removeConstraints:constraints]; } else { - //Item insertion - - previousView = [self visibleViewBeforeIndex:stackIndex]; - nextView = [self visibleViewAfterIndex:newItem ? stackIndex - 1: stackIndex]; - - NSArray *constraints; - BOOL isLastVisibleItem = [self isViewLastItem:previousView excludingItem:view]; - BOOL isFirstVisibleView = previousView == nil; - BOOL isOnlyItem = previousView == nil && nextView == nil; - - if (isLastVisibleItem) { - constraints = @[[self lastViewConstraint]]; - } else if(isOnlyItem) { - constraints = [self constraintsBetweenView:previousView ?: self andView:nextView ?: self inAxis:self.axis]; - } else if(isFirstVisibleView) { - constraints = @[[self firstViewConstraint]]; - } else { - constraints = [self constraintsBetweenView:previousView ?: self andView:nextView ?: self inAxis:self.axis]; - } + NSAssert(previousView && nextView, @"Should have both a next and previous view"); + //Item insertion, never as last or first view + NSArray *constraints = [self constraintsBetweenView:previousView andView:nextView inAxis:self.axis]; [self removeConstraints:constraints]; - - if (newItem) { - [self insertSubview:view atIndex:stackIndex]; - } } [self.distributionStrategy alignView:view afterView:previousView]; @@ -255,36 +251,46 @@ - (void)insertArrangedSubview:(UIView *)view atIndex:(NSUInteger)stackIndex newI [self.distributionStrategy alignView:nextView afterView:view]; } -- (void)removeViewFromArrangedViews:(UIView*)view permanently:(BOOL)permanently { - NSInteger index = [self.subviews indexOfObject:view]; - if (index == NSNotFound) { return; } - - id previousView = [self visibleViewBeforeView:view]; - id nextView = [self visibleViewAfterView:view]; +- (void)willRemoveArrangedSubview:(UIView *)view { + id previousView = [self arrangedSubviewBeforeView:view]; + id nextView = [self arrangedSubviewAfterView:view]; - if (permanently) { - [view removeFromSuperview]; - } else { - NSArray *constraint = [self constraintsAffectingView:view]; - [self removeConstraints:constraint]; - } + NSArray *constraint = [self constraintsAffectingView:view]; + [self removeConstraints:constraint]; if (nextView) { [self.distributionStrategy alignView:nextView afterView:previousView]; } else if(previousView) { - [self.distributionStrategy alignView:nil afterView:[self lastVisibleItem]]; + [self.distributionStrategy alignView:nil afterView:previousView]; } } #pragma mark - Hide and Unhide - (void)hideView:(UIView*)view { - [self removeViewFromArrangedViews:view permanently:NO]; + [self removeArrangedSubview:view]; } - (void)unHideView:(UIView*)view { + // insert the view just before the next arranged view + NSInteger index = [self.subviews indexOfObject:view]; - [self insertArrangedSubview:view atIndex:index newItem:NO]; + NSAssert(index != NSNotFound, @"Cannot handle a view that is becoming visible that is not our subview"); + if (index != NSNotFound) { + UIView *nextArrangedSubview = nil; + NSUInteger i = index+1; + for (; i < [self.subviews count] && !nextArrangedSubview; i++) { + UIView *view = self.subviews[i]; + if ([_mutableArrangedSubviews containsObject:view]) { + nextArrangedSubview = view; + } + } + if (nextArrangedSubview) { + [self insertArrangedSubview:view atIndex:[self indexInArrangedSubviewsOfObject:nextArrangedSubview]]; + } else { + [self addArrangedSubview:view]; + } + } } #pragma mark - Align View @@ -292,19 +298,57 @@ - (void)unHideView:(UIView*)view { - (void)layoutArrangedViews { [self removeDecendentConstraints]; - [self iterateVisibleViews:^(UIView *view, UIView *previousView) { + [self iterateArrangedSubviews:^(UIView *view, UIView *previousView) { [self.distributionStrategy alignView:view afterView:previousView]; [self.alignmentStrategy addConstraintsOnOtherAxis:view]; }]; - [self.distributionStrategy alignView:nil afterView:[self lastVisibleItem]]; + [self.distributionStrategy alignView:nil afterView:[self lastArrangedSubview]]; } -- (void)addViewsAsSubviews:(NSArray*)views { - for (UIView *view in views) { - view.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:view]; - } +- (void)didAddSubview:(UIView *)subview { + [self addObserverForView:subview]; +} + +- (void)willRemoveSubview:(UIView *)subview { + [self removeArrangedSubview:subview]; + [self removeObserverForView:subview]; +} + +#pragma mark KVO-compatible Mutable Indexed Accessors for arrangedSubviews +- (NSUInteger)countOfArrangedSubviews { + return [_mutableArrangedSubviews count]; +} + +- (UIView *)objectInArrangedSubviewsAtIndex:(NSUInteger)index { + return [_mutableArrangedSubviews objectAtIndex:index]; +} + +- (NSUInteger)indexInArrangedSubviewsOfObject:(UIView *)view { + return [_mutableArrangedSubviews indexOfObject:view]; +} + +- (void)insertObject:(UIView *)object inArrangedSubviewsAtIndex:(NSUInteger)index { + [_mutableArrangedSubviews insertObject:object atIndex:index]; + [self didAddArrangedSubview:object]; +} + +- (void)removeObjectFromArrangedSubviewsAtIndex:(NSUInteger)index { + UIView *view = _mutableArrangedSubviews[index]; + [self willRemoveArrangedSubview:view]; + [_mutableArrangedSubviews removeObjectAtIndex:index]; +} + +- (NSArray * __nonnull)arrangedSubviews { + return [_mutableArrangedSubviews copy]; +} + +- (UIView *)firstArrangedSubview { + return [_mutableArrangedSubviews firstObject]; +} + +- (UIView *)lastArrangedSubview { + return [_mutableArrangedSubviews lastObject]; } @end diff --git a/Pod/Classes/OAStackView_ArrangedSubviews.h b/Pod/Classes/OAStackView_ArrangedSubviews.h new file mode 100644 index 0000000..5edab9e --- /dev/null +++ b/Pod/Classes/OAStackView_ArrangedSubviews.h @@ -0,0 +1,27 @@ +// +// OAStackView_ArrangedSubviews.h +// Pods +// +// Created by Thomas Visser on 25/06/15. +// +// + +#import + +@interface OAStackView () + +- (NSUInteger)countOfArrangedSubviews; + +- (nonnull UIView*)objectInArrangedSubviewsAtIndex:(NSUInteger)index; + +- (NSUInteger)indexInArrangedSubviewsOfObject:(nonnull UIView *)view; + +- (void)insertObject:(nullable UIView *)object inArrangedSubviewsAtIndex:(NSUInteger)index; + +- (void)removeObjectFromArrangedSubviewsAtIndex:(NSUInteger)index; + +- (nullable UIView *)firstArrangedSubview; + +- (nullable UIView *)lastArrangedSubview; + +@end From d82d1d23df52020658eca17148a190500af080f5 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Thu, 25 Jun 2015 15:06:30 +0200 Subject: [PATCH 03/10] fixed failing test by adding a new arrangedSubview as a subview --- Pod/Classes/OAStackView.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index 40e9d15..6f9e82b 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -195,8 +195,7 @@ - (CGSize)intrinsicContentSize { #pragma mark - Adding and removing -- (void)addArrangedSubviews:(NSArray *)views -{ +- (void)addArrangedSubviews:(NSArray *)views { for (UIView *view in views) { [self addArrangedSubview:view]; } @@ -207,6 +206,7 @@ - (void)addArrangedSubview:(UIView *)view { } - (void)insertArrangedSubview:(UIView * __nonnull)view atIndex:(NSUInteger)stackIndex { + [self addSubview:view]; [self insertObject:view inArrangedSubviewsAtIndex:stackIndex]; } From 3058581a0ff531cb6b85a6fba85e267580706fd2 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Fri, 26 Jun 2015 11:42:47 +0200 Subject: [PATCH 04/10] replaced intrinsicContentSize logic by adding additional constraints in the aligning axis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prevents arranged subviews from growing outside the stack view’s frame. This could happen if the size of the stackview was constrained so it could not become its intrinsic size. --- Pod/Classes/OAStackView.m | 23 ------------------- Pod/Classes/OAStackViewAlignmentStrategy.m | 26 +++++++++++++++------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index 6f9e82b..8b2ef18 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -168,29 +168,6 @@ - (void)setDistributionValue:(NSInteger)distributionValue { - (void)layoutSubviews { [super layoutSubviews]; - [self invalidateIntrinsicContentSize]; -} - -- (CGSize)intrinsicContentSize { - CGSize size = [super intrinsicContentSize]; - - __block float maxSize = 0; - - [self iterateArrangedSubviews:^(UIView *view, UIView *previousView) { - if (self.axis == UILayoutConstraintAxisVertical) { - maxSize = fmaxf(maxSize, CGRectGetWidth(view.frame)); - } else { - maxSize = fmaxf(maxSize, CGRectGetHeight(view.frame)); - } - }]; - - if (self.axis == UILayoutConstraintAxisVertical) { - size.width = maxSize; - } else { - size.height = maxSize; - } - - return size; } #pragma mark - Adding and removing diff --git a/Pod/Classes/OAStackViewAlignmentStrategy.m b/Pod/Classes/OAStackViewAlignmentStrategy.m index 631ab5a..7e7778e 100644 --- a/Pod/Classes/OAStackViewAlignmentStrategy.m +++ b/Pod/Classes/OAStackViewAlignmentStrategy.m @@ -113,7 +113,7 @@ @implementation OAStackViewAlignmentStrategyLeading - (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view { - id constraintString = [NSString stringWithFormat:@"%@:|-0-[view]", [self otherAxisString]]; + id constraintString = [NSString stringWithFormat:@"%@:|-0-[view]-(>=0)-|", [self otherAxisString]]; return [NSLayoutConstraint constraintsWithVisualFormat:constraintString options:0 @@ -127,7 +127,7 @@ @implementation OAStackViewAlignmentStrategyTrailing - (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view { - id constraintString = [NSString stringWithFormat:@"%@:[view]-0-|", [self otherAxisString]]; + id constraintString = [NSString stringWithFormat:@"%@:|-(>=0)-[view]-0-|", [self otherAxisString]]; return [NSLayoutConstraint constraintsWithVisualFormat:constraintString options:0 @@ -141,12 +141,22 @@ @implementation OAStackViewAlignmentStrategyCenter - (NSArray*)constraintsalignViewOnOtherAxis:(UIView*)view { - return @[[NSLayoutConstraint constraintWithItem:view - attribute:[self centerAttribute] - relatedBy:NSLayoutRelationEqual - toItem:view.superview - attribute:[self centerAttribute] - multiplier:1 constant:0]]; + NSString *constraintString = [NSString stringWithFormat:@"%@:|-(>=0)-[view]-(>=0)-|", [self otherAxisString]]; + + NSArray *edgeConstraints = [NSLayoutConstraint constraintsWithVisualFormat:constraintString + options:0 + metrics:nil + views:NSDictionaryOfVariableBindings(view)]; + + NSLayoutConstraint *centerContraint = [NSLayoutConstraint constraintWithItem:view + attribute:[self centerAttribute] + relatedBy:NSLayoutRelationEqual + toItem:view.superview + attribute:[self centerAttribute] + multiplier:1 + constant:0]; + + return [edgeConstraints arrayByAddingObject:centerContraint]; } @end From ffcde3bc8bcf15213a83f2729b3ce98d9f54b49a Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Fri, 26 Jun 2015 11:43:14 +0200 Subject: [PATCH 05/10] added missing NSLayoutAttributes --- Pod/Classes/OAStackView+Constraint.m | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Pod/Classes/OAStackView+Constraint.m b/Pod/Classes/OAStackView+Constraint.m index d4e7582..b5e5e25 100644 --- a/Pod/Classes/OAStackView+Constraint.m +++ b/Pod/Classes/OAStackView+Constraint.m @@ -21,8 +21,7 @@ - (NSArray*)constraintsBetweenView:(UIView*)firstView andView:(UIView*)otherView viewMatches = viewMatches || (firstView == constraint.secondItem && otherView == constraint.firstItem); } - BOOL isCorrectAxis = [self isConstraintAttribute:constraint.firstAttribute affectingAxis:axis] || - [self isConstraintAttribute:constraint.secondAttribute affectingAxis:axis]; + BOOL isCorrectAxis = [self isConstraint:constraint affectingAxis:axis]; if (viewMatches && isCorrectAxis) { [arr addObject:constraint]; @@ -141,15 +140,29 @@ - (BOOL)isConstraintAttribute:(NSLayoutAttribute)attribute affectingAxis:(UILayo case UILayoutConstraintAxisHorizontal: return attribute == NSLayoutAttributeLeft || attribute == NSLayoutAttributeRight || attribute == NSLayoutAttributeLeading || attribute == NSLayoutAttributeTrailing || - attribute == NSLayoutAttributeCenterX || attribute == NSLayoutAttributeWidth; + attribute == NSLayoutAttributeCenterX || attribute == NSLayoutAttributeWidth +#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_1 + || attribute == NSLayoutAttributeLeftMargin || attribute == NSLayoutAttributeRightMargin || + attribute == NSLayoutAttributeLeadingMargin || attribute == NSLayoutAttributeTrailingMargin || + attribute == NSLayoutAttributeCenterXWithinMargins +#endif + ; break; case UILayoutConstraintAxisVertical: return attribute == NSLayoutAttributeTop || attribute == NSLayoutAttributeBottom || - attribute == NSLayoutAttributeCenterY || attribute == NSLayoutAttributeHeight; + attribute == NSLayoutAttributeCenterY || attribute == NSLayoutAttributeHeight || + attribute == NSLayoutAttributeBaseline +#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_1 + || attribute == NSLayoutAttributeLastBaseline || attribute == NSLayoutAttributeFirstBaseline || + attribute == NSLayoutAttributeTopMargin || attribute == NSLayoutAttributeBottomMargin || + attribute == NSLayoutAttributeCenterYWithinMargins +#endif + ; break; default: + return NO; break; } } From 4563c25b8b510a28637b9e1139051dc6395435ac Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Fri, 26 Jun 2015 11:44:09 +0200 Subject: [PATCH 06/10] fixed issues when inserting an already arranged subview --- Pod/Classes/OAStackView.m | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index 8b2ef18..f1871ef 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -183,8 +183,21 @@ - (void)addArrangedSubview:(UIView *)view { } - (void)insertArrangedSubview:(UIView * __nonnull)view atIndex:(NSUInteger)stackIndex { + NSUInteger previousIndex = [self indexInArrangedSubviewsOfObject:view]; + NSUInteger newIndex = stackIndex; + + if (previousIndex == newIndex) { return; } + + if (previousIndex != NSNotFound) { + // view was already arranged, remove first + [self removeArrangedSubview:view]; + if (previousIndex < newIndex) { + newIndex--; + } + } + [self addSubview:view]; - [self insertObject:view inArrangedSubviewsAtIndex:stackIndex]; + [self insertObject:view inArrangedSubviewsAtIndex:newIndex]; } - (void)removeArrangedSubview:(UIView *)view { From fc9752247a3477de03b9f849349f1949f8d74b71 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Fri, 26 Jun 2015 11:47:17 +0200 Subject: [PATCH 07/10] added viewForBaselineLayout to fix baseline alignment for recursive stack views --- Pod/Classes/OAStackView.m | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index f1871ef..53a4065 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -305,6 +305,25 @@ - (void)willRemoveSubview:(UIView *)subview { [self removeObserverForView:subview]; } +- (UIView *)viewForBaselineLayout { + UIView *res = nil; + if (self.axis == UILayoutConstraintAxisHorizontal) { + for (UIView *arrangedView in _mutableArrangedSubviews) { + if (!res || CGRectGetHeight(res.frame) < CGRectGetHeight(arrangedView.frame)) { + res = arrangedView; + } + } + + } else { + res = [self lastArrangedSubview]; + } + + if ([res isKindOfClass:[self class]]) { + res = [res viewForBaselineLayout]; + } + return res; +} + #pragma mark KVO-compatible Mutable Indexed Accessors for arrangedSubviews - (NSUInteger)countOfArrangedSubviews { return [_mutableArrangedSubviews count]; From ad2f79b767f420e09b23f398653fd37951ce62fb Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Fri, 26 Jun 2015 11:48:12 +0200 Subject: [PATCH 08/10] fixes a case where duplicate constraints could be added --- Pod/Classes/OAStackView.m | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index 53a4065..263f308 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -220,10 +220,6 @@ - (void)didAddArrangedSubview:(UIView *)view { NSArray *constraints = [self lastConstraintAffectingView:self andView:previousView inAxis:self.axis]; [self removeConstraints:constraints]; - - if ([self firstArrangedSubview] == previousView) { - [self.distributionStrategy alignView:previousView afterView:nil]; - } } else if (stackIndex == 0) { // Prepending a new item NSArray *constraints = [self firstConstraintAffectingView:self andView:nextView inAxis:self.axis]; @@ -245,14 +241,10 @@ - (void)willRemoveArrangedSubview:(UIView *)view { id previousView = [self arrangedSubviewBeforeView:view]; id nextView = [self arrangedSubviewAfterView:view]; - NSArray *constraint = [self constraintsAffectingView:view]; - [self removeConstraints:constraint]; + NSArray *constraints = [self constraintsAffectingView:view]; + [self removeConstraints:constraints]; - if (nextView) { - [self.distributionStrategy alignView:nextView afterView:previousView]; - } else if(previousView) { - [self.distributionStrategy alignView:nil afterView:previousView]; - } + [self.distributionStrategy alignView:nextView afterView:previousView]; } #pragma mark - Hide and Unhide From a6b7eff20333ca0561fb690d2852b5e84e272429 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Sat, 27 Jun 2015 11:27:28 +0200 Subject: [PATCH 09/10] updated nullability annotations for older compilers --- Pod/Classes/OAStackView.m | 10 +++++----- Pod/Classes/OAStackView_ArrangedSubviews.h | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Pod/Classes/OAStackView.m b/Pod/Classes/OAStackView.m index 263f308..91849ee 100644 --- a/Pod/Classes/OAStackView.m +++ b/Pod/Classes/OAStackView.m @@ -321,15 +321,15 @@ - (NSUInteger)countOfArrangedSubviews { return [_mutableArrangedSubviews count]; } -- (UIView *)objectInArrangedSubviewsAtIndex:(NSUInteger)index { +- (UIView * __nonnull)objectInArrangedSubviewsAtIndex:(NSUInteger)index { return [_mutableArrangedSubviews objectAtIndex:index]; } -- (NSUInteger)indexInArrangedSubviewsOfObject:(UIView *)view { +- (NSUInteger)indexInArrangedSubviewsOfObject:(UIView * __nonnull)view { return [_mutableArrangedSubviews indexOfObject:view]; } -- (void)insertObject:(UIView *)object inArrangedSubviewsAtIndex:(NSUInteger)index { +- (void)insertObject:(UIView * __nonnull)object inArrangedSubviewsAtIndex:(NSUInteger)index { [_mutableArrangedSubviews insertObject:object atIndex:index]; [self didAddArrangedSubview:object]; } @@ -344,11 +344,11 @@ - (NSArray * __nonnull)arrangedSubviews { return [_mutableArrangedSubviews copy]; } -- (UIView *)firstArrangedSubview { +- (UIView * __nullable)firstArrangedSubview { return [_mutableArrangedSubviews firstObject]; } -- (UIView *)lastArrangedSubview { +- (UIView * __nullable)lastArrangedSubview { return [_mutableArrangedSubviews lastObject]; } diff --git a/Pod/Classes/OAStackView_ArrangedSubviews.h b/Pod/Classes/OAStackView_ArrangedSubviews.h index 5edab9e..d37ffdc 100644 --- a/Pod/Classes/OAStackView_ArrangedSubviews.h +++ b/Pod/Classes/OAStackView_ArrangedSubviews.h @@ -12,16 +12,16 @@ - (NSUInteger)countOfArrangedSubviews; -- (nonnull UIView*)objectInArrangedSubviewsAtIndex:(NSUInteger)index; +- (UIView* __nonnull)objectInArrangedSubviewsAtIndex:(NSUInteger)index; -- (NSUInteger)indexInArrangedSubviewsOfObject:(nonnull UIView *)view; +- (NSUInteger)indexInArrangedSubviewsOfObject:(UIView * __nonnull)view; -- (void)insertObject:(nullable UIView *)object inArrangedSubviewsAtIndex:(NSUInteger)index; +- (void)insertObject:(UIView * __nonnull)object inArrangedSubviewsAtIndex:(NSUInteger)index; - (void)removeObjectFromArrangedSubviewsAtIndex:(NSUInteger)index; -- (nullable UIView *)firstArrangedSubview; +- (UIView * __nullable)firstArrangedSubview; -- (nullable UIView *)lastArrangedSubview; +- (UIView * __nullable)lastArrangedSubview; @end From f9c6509660de59c5fa5d7d7799c4bcefe7301ac3 Mon Sep 17 00:00:00 2001 From: Thomas Visser Date: Sat, 27 Jun 2015 11:34:08 +0200 Subject: [PATCH 10/10] added #define of __nonnull for older compilers --- Pod/Classes/OAStackView.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Pod/Classes/OAStackView.h b/Pod/Classes/OAStackView.h index 8b8931a..d1c20df 100644 --- a/Pod/Classes/OAStackView.h +++ b/Pod/Classes/OAStackView.h @@ -76,8 +76,9 @@ typedef NS_ENUM(NSInteger, OAStackViewAlignment) { #define NS_ASSUME_NONNULL_BEGIN #define NS_ASSUME_NONNULL_END #define nullable -#define nonnullable +#define nonnull #define __nullable +#define __nonnull #endif NS_ASSUME_NONNULL_BEGIN