Visitar URL original
Add dirty version of ORHorizontalStackView · orta/ORStackView@00eaa79 · GitHub
Skip to content

Commit 00eaa79

Browse files
committed
Add dirty version of ORHorizontalStackView
1 parent 0186564 commit 00eaa79

4 files changed

Lines changed: 345 additions & 12 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//
2+
// ORHorizontalStackView.h
3+
// ORStackView
4+
//
5+
// Created by Denis Lebedev on 4/23/15.
6+
//
7+
//
8+
9+
@interface ORHorizontalStackView : UIView
10+
11+
/// Adds a view to the heirarchy, if it's the first it is
12+
/// attached to the top, otherwise to the view above.
13+
- (void)addSubview:(UIView *)view withTopMargin:(CGFloat)margin;
14+
15+
/// Adds a view to the heirarchy like addSubview:withTopMargin:
16+
/// will also center and apply side margins as insets from edge
17+
- (void)addSubview:(UIView *)view withTopMargin:(CGFloat)topMargin sideMargin:(CGFloat)sideMargin;
18+
19+
/// Adds a view controller's view to the stack heirarchy
20+
- (void)addViewController:(UIViewController *)viewController toParent:(UIViewController *)parentViewController withTopMargin:(CGFloat)margin;
21+
22+
/// Adds a view controller's view to the stack heirarchy, and applys edge insets
23+
- (void)addViewController:(UIViewController *)viewController toParent:(UIViewController *)parentViewController withTopMargin:(CGFloat)margin sideMargin:(CGFloat)sideMargin;
24+
25+
26+
// Note: These indexes are not z-order, but stack order.
27+
// z-index ordering can be done with the UIView methods bringSubviewToFront:
28+
29+
/// Insert a subview at an arbitrary index in the stack's order
30+
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index withTopMargin:(CGFloat)margin;
31+
32+
/// Inserts a subview and centeres it
33+
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index withTopMargin:(CGFloat)topMargin sideMargin:(CGFloat)sideMargin;
34+
35+
/// Insert a subview below another view, or at the end if it cannot be found
36+
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview withTopMargin:(CGFloat)margin;
37+
38+
/// Insert a subview above another view, will assert if view is not found
39+
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview withTopMargin:(CGFloat)margin;
40+
41+
/// Remove a subview from the Stack View
42+
- (void)removeSubview:(UIView *)subview;
43+
44+
/// Remove all subviews from the Stack View
45+
- (void)removeAllSubviews;
46+
47+
/// Perform insertion / removals without updating the constraints
48+
- (void)performBatchUpdates:(void (^)(void))updates;
49+
50+
51+
// Useful getters
52+
53+
/// Returns the top constraint for a specific view
54+
- (NSLayoutConstraint *)topConstraintForView:(UIView *)view;
55+
56+
/// Returns the lowest view in the stack.
57+
- (UIView *)lastView;
58+
59+
/// Setting this creates a bottom constraint letting the ORStackView set it's own height, defaults to 0, use NSNotFound to not create a bttom constraint.
60+
@property (nonatomic, assign) CGFloat trailingMarginWidth;
61+
62+
/// Settings this will create a constraint on the top view making the view sit below the layout guide (and the top margin)
63+
@property (nonatomic, strong) id<UILayoutSupport> topLayoutGuide;
64+
65+
/// I'd prefer you to not use the UIView subview APIs please, things will break.
66+
- (void)addSubview:(UIView *)view __attribute__((unavailable("addSubview is not supported on ORStackView.")));
67+
68+
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index __attribute__((unavailable("insertSubview is not supported on ORStackView.")));
69+
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview __attribute__((unavailable("insertSubview is not supported on ORStackView.")));
70+
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview __attribute__((unavailable("insertSubview is not supported on ORStackView.")));
71+
72+
@end
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
//
2+
// ORHorizontalStackView.m
3+
// ORStackView
4+
//
5+
// Created by Dzianis Lebedzeu on 4/23/15.
6+
//
7+
//
8+
9+
#import "ORHorizontalStackView.h"
10+
#import <FLKAutoLayout/UIView+FLKAutoLayout.h>
11+
#import "ORStackView+Private.h"
12+
13+
@interface ORHorizontalStackView()
14+
15+
/// Delay updating constraints when true
16+
@property (nonatomic, assign) BOOL batchingUpdates;
17+
@property (nonatomic, strong) NSLayoutConstraint *trailingConstraint;
18+
19+
20+
// TODO: move to private
21+
22+
/// An array of StackView objects in the order they should be shown
23+
@property (nonatomic, strong) NSMutableArray *viewStack;
24+
25+
@end
26+
27+
@implementation ORHorizontalStackView
28+
29+
- (instancetype)initWithFrame:(CGRect)frame
30+
{
31+
self = [super initWithFrame:frame];
32+
if (!self) return nil;
33+
34+
[self _setup];
35+
36+
return self;
37+
}
38+
39+
- (instancetype)init
40+
{
41+
return [self initWithFrame:CGRectZero];
42+
}
43+
44+
- (void)awakeFromNib
45+
{
46+
[self _setup];
47+
}
48+
49+
- (void)_setup
50+
{
51+
_viewStack = [NSMutableArray array];
52+
_trailingMarginWidth = 0;
53+
}
54+
55+
- (void)updateConstraints
56+
{
57+
// Remove all constraints
58+
for (StackView *stackView in self.viewStack) {
59+
[self removeConstraint:stackView.topConstraint];
60+
}
61+
62+
// Add the new constraints
63+
for (StackView *stackView in self.viewStack) {
64+
UIView *view = stackView.view;
65+
NSString *predicate = @(stackView.constant).stringValue;
66+
NSInteger index = [self.viewStack indexOfObject:stackView];
67+
68+
if (index == 0) {
69+
stackView.topConstraint = [[view alignLeadingEdgeWithView:self predicate:predicate] lastObject];
70+
} else {
71+
UIView *viewAbove = [self.viewStack[index - 1] view];
72+
stackView.topConstraint = [[view constrainLeadingSpaceToView:viewAbove predicate:predicate] lastObject];
73+
}
74+
}
75+
76+
if (self.trailingMarginWidth != NSNotFound) {
77+
[self removeConstraint:self.trailingConstraint];
78+
UIView *lastView = self.lastView;
79+
if (self.lastView) {
80+
NSString *constraint = [NSString stringWithFormat:@"%0.0f", self.trailingMarginWidth];
81+
self.trailingConstraint = [[self alignTrailingEdgeWithView:lastView predicate:constraint] lastObject];
82+
}
83+
}
84+
85+
[super updateConstraints];
86+
}
87+
88+
#pragma mark - Adding Subviews
89+
90+
- (void)addSubview:(UIView *)view withTopMargin:(CGFloat)margin
91+
{
92+
[self _addSubview:view withTopMargin:margin centered:NO sideMargin:0];
93+
}
94+
95+
- (void)addSubview:(UIView *)view withTopMargin:(CGFloat)topMargin sideMargin:(CGFloat)sideMargin
96+
{
97+
[self _addSubview:view withTopMargin:topMargin centered:YES sideMargin:sideMargin];
98+
}
99+
100+
- (void)_addSubview:(UIView *)view withTopMargin:(CGFloat)topMargin centered:(BOOL)centered sideMargin:(CGFloat)sideMargin
101+
{
102+
NSInteger index = self.viewStack.count;
103+
[self _insertSubview:view atIndex:index withTopMargin:topMargin centered:centered sideMargin:sideMargin];
104+
}
105+
106+
#pragma mark - Inserting Subviews
107+
108+
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index withTopMargin:(CGFloat)margin;
109+
{
110+
[self _insertSubview:view atIndex:index withTopMargin:margin centered:NO sideMargin:0];
111+
}
112+
113+
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index withTopMargin:(CGFloat)topMargin sideMargin:(CGFloat)sideMargin
114+
{
115+
[self _insertSubview:view atIndex:index withTopMargin:topMargin centered:YES sideMargin:sideMargin];
116+
}
117+
118+
119+
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview withTopMargin:(CGFloat)margin
120+
{
121+
BOOL hasSibling = [self.subviews containsObject:siblingSubview];
122+
NSInteger index = hasSibling ? [self indexOfView:siblingSubview] : self.viewStack.count;
123+
[self _insertSubview:view atIndex:index withTopMargin:margin centered:NO sideMargin:0];
124+
}
125+
126+
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview withTopMargin:(CGFloat)margin
127+
{
128+
NSAssert([self.subviews containsObject:siblingSubview], @"SiblingSubview not found in ORStackView");
129+
130+
NSInteger index = [self indexOfView:siblingSubview] - 1;
131+
[self _insertSubview:view atIndex:index withTopMargin:margin centered:NO sideMargin:0];
132+
}
133+
134+
- (void)_insertSubview:(UIView *)view atIndex:(NSInteger)index withTopMargin:(CGFloat)topMargin centered:(BOOL)centered sideMargin:(CGFloat)sideMargin
135+
{
136+
NSParameterAssert(view);
137+
if ([self.subviews containsObject:view]) return;
138+
139+
[super addSubview:view];
140+
141+
StackView *stackView = [[StackView alloc] init];
142+
stackView.view = view;
143+
stackView.constant = topMargin;
144+
[self.viewStack insertObject:stackView atIndex:index];
145+
146+
// NSString *vhl = [NSString stringWithFormat:@"V:[topLayoutGuide]-%@-[view]", predicate];
147+
// NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:vhl options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(view, topLayoutGuide)];
148+
// [self addConstraints:constraints];
149+
// stackView.topConstraint = [constraints firstObject];
150+
151+
if (centered) {
152+
//TODO: handle topLayoutGuide here
153+
[view constrainHeightToView:self predicate:@(-sideMargin).stringValue];
154+
[view alignCenterYWithView:self predicate:nil];
155+
}
156+
157+
if (!self.batchingUpdates) [self setNeedsUpdateConstraints];
158+
}
159+
160+
#pragma mark Removal
161+
162+
- (void)removeSubview:(UIView *)subview
163+
{
164+
if (![self.subviews containsObject:subview]) return;
165+
166+
[subview removeFromSuperview];
167+
168+
for (StackView *stackView in self.viewStack.copy) {
169+
if ([subview isEqual:stackView.view]) {
170+
[self.viewStack removeObject:stackView];
171+
}
172+
}
173+
174+
if (!self.batchingUpdates) [self setNeedsUpdateConstraints];
175+
}
176+
177+
- (void)removeAllSubviews
178+
{
179+
for (StackView *stackView in [self.viewStack copy]) {
180+
[self.viewStack removeObject:stackView];
181+
[stackView.view removeFromSuperview];
182+
}
183+
184+
if (!self.batchingUpdates) [self setNeedsUpdateConstraints];
185+
}
186+
187+
#pragma mark Batching
188+
189+
- (void)performBatchUpdates:(void (^)(void))updates;
190+
{
191+
NSParameterAssert(updates);
192+
193+
self.batchingUpdates = YES;
194+
updates();
195+
self.batchingUpdates = NO;
196+
197+
[self setNeedsUpdateConstraints];
198+
}
199+
200+
- (void)addViewController:(UIViewController *)viewController toParent:(UIViewController *)parentViewController withTopMargin:(CGFloat)margin
201+
{
202+
[viewController willMoveToParentViewController:parentViewController];
203+
[parentViewController addChildViewController:viewController];
204+
[self addSubview:viewController.view withTopMargin:margin];
205+
[viewController didMoveToParentViewController:parentViewController];
206+
}
207+
208+
- (void)addViewController:(UIViewController *)viewController toParent:(UIViewController *)parentViewController withTopMargin:(CGFloat)margin sideMargin:(CGFloat)sideMargin;
209+
{
210+
[viewController willMoveToParentViewController:parentViewController];
211+
[parentViewController addChildViewController:viewController];
212+
[self addSubview:viewController.view withTopMargin:margin sideMargin:sideMargin];
213+
[viewController didMoveToParentViewController:parentViewController];
214+
}
215+
216+
217+
#pragma mark Helper functions
218+
219+
- (NSInteger)indexOfView:(UIView *)view
220+
{
221+
for (StackView *stackView in self.viewStack) {
222+
if ([view isEqual:stackView.view]) {
223+
return [self.viewStack indexOfObject:stackView];
224+
}
225+
}
226+
return NSNotFound;
227+
}
228+
229+
- (NSLayoutConstraint *)topConstraintForView:(UIView *)view
230+
{
231+
for (StackView *stackView in self.viewStack) {
232+
if ([view isEqual:stackView.view]) {
233+
return stackView.topConstraint;
234+
}
235+
}
236+
return nil;
237+
}
238+
239+
- (UIView *)lastView
240+
{
241+
return [[self.viewStack lastObject] view];
242+
}
243+
244+
#pragma mark - Layout Guides
245+
246+
- (void)setTopLayoutGuide:(id<UILayoutSupport>)topLayoutGuide {
247+
_topLayoutGuide = topLayoutGuide;
248+
[self needsUpdateConstraints];
249+
}
250+
251+
@end

0 commit comments

Comments
 (0)