diff --git a/CSSLayoutKit/Tests/CSSLayoutKitTests.m b/CSSLayoutKit/Tests/CSSLayoutKitTests.m index 29071e77..720e0cb2 100644 --- a/CSSLayoutKit/Tests/CSSLayoutKitTests.m +++ b/CSSLayoutKit/Tests/CSSLayoutKitTests.m @@ -125,4 +125,78 @@ XCTAssertEqual(containerSize.width, totalWidth, @"The container's width is %.6f, the subviews take up %.6f", containerSize.width, totalWidth); } +- (void)testThatWeRespectIncludeInLayoutFlag +{ + const CGSize containerSize = CGSizeMake(300, 50); + + UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)]; + [container css_setUsesFlexbox:YES]; + [container css_setFlexDirection:CSSFlexDirectionRow]; + + UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview1 css_setUsesFlexbox:YES]; + [subview1 css_setFlexGrow:1]; + [container addSubview:subview1]; + + UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview2 css_setUsesFlexbox:YES]; + [subview2 css_setFlexGrow:1]; + [container addSubview:subview2]; + + UIView *subview3 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview3 css_setUsesFlexbox:YES]; + [subview3 css_setFlexGrow:1]; + [container addSubview:subview3]; + + [container css_applyLayout]; + + for (UIView *view in container.subviews) { + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(view.bounds.size)); + } + + [subview3 css_setIncludeInLayout:NO]; + [container css_applyLayout]; + + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview1.bounds.size)); + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview2.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview2.bounds.size)); + + // We don't set the frame to zero, so, it should be set to what it was previously at. + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 50), subview3.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview3.bounds.size)); +} + +- (void)testThatViewNotIncludedInFirstLayoutPassAreIncludedInSecond +{ + UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 50)]; + [container css_setUsesFlexbox:YES]; + [container css_setFlexDirection:CSSFlexDirectionRow]; + + UIView *subview1 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview1 css_setUsesFlexbox:YES]; + [subview1 css_setFlexGrow:1]; + [container addSubview:subview1]; + + UIView *subview2 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview2 css_setUsesFlexbox:YES]; + [subview2 css_setFlexGrow:1]; + [container addSubview:subview2]; + + UIView *subview3 = [[UIView alloc] initWithFrame:CGRectZero]; + [subview3 css_setUsesFlexbox:YES]; + [subview3 css_setFlexGrow:1]; + [subview3 css_setIncludeInLayout:NO]; + [container addSubview:subview3]; + + [container css_applyLayout]; + + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview1.bounds.size)); + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(150, 50), subview2.bounds.size), @"Actual size is %@", NSStringFromCGSize(subview2.bounds.size)); + XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, subview3.bounds.size), @"Actual size %@", NSStringFromCGSize(subview3.bounds.size)); + + [subview3 css_setIncludeInLayout:YES]; + [container css_applyLayout]; + for (UIView *view in container.subviews) { + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(100, 50), subview1.bounds.size), @"Actual size is %@", NSStringFromCGSize(view.bounds.size)); + } +} + @end diff --git a/CSSLayoutKit/UIView+CSSLayout.h b/CSSLayoutKit/UIView+CSSLayout.h index 5f875687..d7702d09 100644 --- a/CSSLayoutKit/UIView+CSSLayout.h +++ b/CSSLayoutKit/UIView+CSSLayout.h @@ -12,6 +12,11 @@ @interface UIView (CSSLayout) +/** + The property that decides if we should include this view when calculating layout. Defaults to YES. + */ +@property (nonatomic, readwrite, assign, setter=css_setIncludeInLayout:) BOOL css_includeInLayout; + /** The property that decides during layout/sizing whether or not css_* properties should be applied. Defaults to NO. */ diff --git a/CSSLayoutKit/UIView+CSSLayout.m b/CSSLayoutKit/UIView+CSSLayout.m index c5934840..f166b5ff 100644 --- a/CSSLayoutKit/UIView+CSSLayout.m +++ b/CSSLayoutKit/UIView+CSSLayout.m @@ -39,8 +39,23 @@ return [usesFlexbox boolValue]; } +- (BOOL)css_includeInLayout +{ + NSNumber *includeInLayout = objc_getAssociatedObject(self, @selector(css_includeInLayout)); + return (includeInLayout != nil) ? [includeInLayout boolValue] : YES; +} + #pragma mark - Setters +- (void)css_setIncludeInLayout:(BOOL)includeInLayout +{ + objc_setAssociatedObject( + self, + @selector(css_includeInLayout), + @(includeInLayout), + OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + - (void)css_setUsesFlexbox:(BOOL)enabled { objc_setAssociatedObject( @@ -261,17 +276,24 @@ static void CSSAttachNodesFromViewHierachy(UIView *view) { } else { CSSNodeSetMeasureFunc(node, NULL); + NSUInteger numSubviewsInLayout = 0; // Add any children which were added since the last call to css_applyLayout for (NSUInteger i = 0; i < view.subviews.count; i++) { - CSSNodeRef childNode = [view.subviews[i] cssNode]; + UIView *const subview = view.subviews[i]; + if (![subview css_includeInLayout]) { + continue; + } + numSubviewsInLayout++; + + CSSNodeRef childNode = [subview cssNode]; if (CSSNodeChildCount(node) < i + 1 || CSSNodeGetChild(node, i) != childNode) { CSSNodeInsertChild(node, childNode, i); } - CSSAttachNodesFromViewHierachy(view.subviews[i]); + CSSAttachNodesFromViewHierachy(subview); } // Remove any children which were removed since the last call to css_applyLayout - while (view.subviews.count < CSSNodeChildCount(node)) { + while (numSubviewsInLayout < CSSNodeChildCount(node)) { CSSNodeRemoveChild(node, CSSNodeGetChild(node, CSSNodeChildCount(node) - 1)); } } @@ -290,8 +312,11 @@ static CGFloat CSSRoundPixelValue(CGFloat value) static void CSSApplyLayoutToViewHierarchy(UIView *view) { NSCAssert([NSThread isMainThread], @"Framesetting should only be done on the main thread."); - CSSNodeRef node = [view cssNode]; + if (![view css_includeInLayout]) { + return; + } + CSSNodeRef node = [view cssNode]; const CGPoint topLeft = { CSSNodeLayoutGetLeft(node), CSSNodeLayoutGetTop(node),