From 29c44e281c3d6a72a91feff2ac02937561cffbb2 Mon Sep 17 00:00:00 2001 From: David Hart Date: Thu, 5 Jan 2017 08:25:01 -0800 Subject: [PATCH] Set nodes dirty when sizeThatFits changes Summary: In YogaKit, nodes are not dirtied when `sizeThatFits` of leaf nodes change. This makes it difficult to reuse views like `UILabel` when their content (text) changes. I wrote a failing test to prove it and wrote a fix by caching the latest value of `sizeThatFits` in `YGNodeBridge` and checking for changes in `YGAttachNodesFromViewHierachy` (renamed to `YGUpdateNodesFromViewHierachy` to make it clear it's not simply reattaching nodes anymore). When a new size is detected, I call `YGNodeMarkDirty` on the leaf node. **Unfortunately**, this makes another test fail for no logical reason (`testThatViewNotIncludedInFirstLayoutPassAreIncludedInSecond`). Either there's something I don't understand or I've un-advertedly unearthed a bug in the C implementation, but in what case would setting a node dirty make the layout return a different size? Closes https://github.com/facebook/yoga/pull/308 Reviewed By: emilsjolander Differential Revision: D4378826 Pulled By: dshahidehpour fbshipit-source-id: cd0640bc863debb9e434370739416e3e33e57a08 --- YogaKit/Tests/YogaKitTests.m | 23 +++++++++++++++++++++++ YogaKit/UIView+Yoga.h | 5 +++++ YogaKit/UIView+Yoga.m | 15 ++++++++++++--- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/YogaKit/Tests/YogaKitTests.m b/YogaKit/Tests/YogaKitTests.m index e4de239a..bdade66e 100644 --- a/YogaKit/Tests/YogaKitTests.m +++ b/YogaKit/Tests/YogaKitTests.m @@ -96,6 +96,29 @@ XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(514,21), containerSize), @"Size is actually %@", NSStringFromCGSize(containerSize)); } +- (void)testThatMarkingLeafsAsDirtyWillTriggerASizeRecalculation +{ + UIView *container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 50)]; + [container yg_setUsesYoga:YES]; + [container yg_setFlexDirection:YGFlexDirectionRow]; + [container yg_setAlignItems:YGAlignFlexStart]; + + UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; + label.text = @"This is a short text."; + label.numberOfLines = 1; + [label yg_setUsesYoga:YES]; + [container addSubview:label]; + + [container yg_applyLayout]; + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(146,21), label.bounds.size), @"Size is actually %@", NSStringFromCGSize(label.bounds.size)); + + label.text = @"This is a slightly longer text."; + [label yg_markDirty]; + + [container yg_applyLayout]; + XCTAssertTrue(CGSizeEqualToSize(CGSizeMake(213,21), label.bounds.size), @"Size is actually %@", NSStringFromCGSize(label.bounds.size)); +} + - (void)testFrameAndOriginPlacement { const CGSize containerSize = CGSizeMake(320, 50); diff --git a/YogaKit/UIView+Yoga.h b/YogaKit/UIView+Yoga.h index 3a5777ef..b565b1ae 100644 --- a/YogaKit/UIView+Yoga.h +++ b/YogaKit/UIView+Yoga.h @@ -75,4 +75,9 @@ */ - (BOOL)yg_isLeaf; +/** + Mark that a view's layout needs to be recalculated. Only works for leaf views. + */ +- (void)yg_markDirty; + @end diff --git a/YogaKit/UIView+Yoga.m b/YogaKit/UIView+Yoga.m index 9774d3e9..cfe0afa0 100644 --- a/YogaKit/UIView+Yoga.m +++ b/YogaKit/UIView+Yoga.m @@ -11,6 +11,8 @@ #import +static const void *kYGNodeBridgeAssociatedKey = &kYGNodeBridgeAssociatedKey; + @interface YGNodeBridge : NSObject @property (nonatomic, assign, readonly) YGNodeRef cnode; @end @@ -39,6 +41,14 @@ @implementation UIView (Yoga) +- (void)yg_markDirty +{ + YGNodeBridge *const bridge = objc_getAssociatedObject(self, kYGNodeBridgeAssociatedKey); + if (bridge != nil && [self yg_isLeaf]) { + YGNodeMarkDirty(bridge.cnode); + } +} + - (BOOL)yg_usesYoga { NSNumber *usesYoga = objc_getAssociatedObject(self, @selector(yg_usesYoga)); @@ -226,13 +236,12 @@ - (YGNodeRef)ygNode { - YGNodeBridge *node = objc_getAssociatedObject(self, @selector(ygNode)); + YGNodeBridge *node = objc_getAssociatedObject(self, kYGNodeBridgeAssociatedKey); if (!node) { node = [YGNodeBridge new]; YGNodeSetContext(node.cnode, (__bridge void *) self); - objc_setAssociatedObject(self, @selector(ygNode), node, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kYGNodeBridgeAssociatedKey, node, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - return node.cnode; }