Skip to content

Commit 798517a

Browse files
Adam Comellafacebook-github-bot
Adam Comella
authored andcommitted
Fix relayout of inline views (#21968)
Summary: If a view inside of an inline view became dirty (e.g. its top/left prop changed), its position would not update on screen. This is because Yoga didn't know the view needed to be relaid out because Yoga's dirty signal didn't propagate all the way up to the root. The problem is that inline views don't have a parent in the Yoga tree causing Yoga's dirtiness signal propagation to get cut off early. The fix is, when an inline views gets dirty, mark the parent Text's Yoga node as dirty. This will cause Yoga's dirtiness signal to propagate all the way up to the root node. Yoga has a hook to inform you when your node is marked as dirty: `YGNodeSetDirtiedFunc`. We leverage this to find out when an inline view's Yoga node gets dirtied. React Native almost handled this case. Everything worked fine as long as the inline view was nested inside of a virtual text node like this: ``` <Text> <Text> <InlineView /> </Text> </Text> ``` However, the bug repros when the inline view is nested in a non-virtual text node: ``` <Text> <InlineView /> </Text> ``` The fix is to move the special dirtiness propagation logic from `RCTVirtualTextShadowView` to `RCTBaseTextShadowView`. **Test Plan** Created an inline view. Tested the following kinds of updates on the inline view's content: - Moved the content - Removed the content - Added the content Tested this for an inline view that is directly inside of a text node as well as one that is nested under a virtual text node. Here's the code I used for the inline view that moved its content after 2 seconds: ``` const RN = require('react-native'); const React = require('react'); export default class InlineView extends React.Component { constructor(props, context) { super(props, context); this.state = { posBottom: false }; } componentDidMount() { super.componentDidMount && super.componentDidMount(); setTimeout(() => { this.setState({ posBottom: true }); }, 2000); } render() { const pos = this.state.posBottom ? 25 : 0; const color = this.state.posBottom ? 'pink' : 'green'; return ( <RN.View style={{ width: 50, height: 50, backgroundColor: 'steelblue'}}> <RN.View style={{ width: 25, height: 25, top: pos, left: pos, backgroundColor: color }} /> </RN.View> ); } } ``` **Release Notes** [IOS] [BUGFIX] [Text] - Fix case where content of inline views didn't get relaid out Adam Comella Microsoft Corp. Pull Request resolved: #21968 Differential Revision: D12873795 Pulled By: shergin fbshipit-source-id: bbc9f5d3ef25063b0015cec8c4aaf2e41ecd60a8
1 parent 2a349f8 commit 798517a

File tree

2 files changed

+38
-35
lines changed

2 files changed

+38
-35
lines changed

Libraries/Text/BaseText/RCTBaseTextShadowView.m

+38
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@
1414

1515
NSString *const RCTBaseTextShadowViewEmbeddedShadowViewAttributeName = @"RCTBaseTextShadowViewEmbeddedShadowViewAttributeName";
1616

17+
static void RCTInlineViewYogaNodeDirtied(YGNodeRef node)
18+
{
19+
// An inline view (a view nested inside of a text node) does not have a parent
20+
// in the Yoga tree. Consequently, we have to manually propagate the inline
21+
// view's dirty signal up through the text nodes. At some point, it'll reach
22+
// the outermost text node which has a Yoga node and then Yoga will take over
23+
// the dirty signal propagation.
24+
RCTShadowView *inlineView = (__bridge RCTShadowView *)YGNodeGetContext(node);
25+
RCTBaseTextShadowView *baseTextShadowView =
26+
(RCTBaseTextShadowView *)inlineView.reactSuperview;
27+
28+
[baseTextShadowView dirtyLayout];
29+
}
30+
1731
@implementation RCTBaseTextShadowView
1832
{
1933
NSAttributedString *_Nullable _cachedAttributedText;
@@ -35,6 +49,30 @@ - (void)setReactTag:(NSNumber *)reactTag
3549
_textAttributes.tag = reactTag;
3650
}
3751

52+
#pragma mark - Life Cycle
53+
54+
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)index
55+
{
56+
[super insertReactSubview:subview atIndex:index];
57+
58+
[self dirtyLayout];
59+
60+
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
61+
YGNodeSetDirtiedFunc(subview.yogaNode, RCTInlineViewYogaNodeDirtied);
62+
}
63+
}
64+
65+
- (void)removeReactSubview:(RCTShadowView *)subview
66+
{
67+
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
68+
YGNodeSetDirtiedFunc(subview.yogaNode, NULL);
69+
}
70+
71+
[self dirtyLayout];
72+
73+
[super removeReactSubview:subview];
74+
}
75+
3876
#pragma mark - attributedString
3977

4078
- (NSAttributedString *)attributedTextWithBaseTextAttributes:(nullable RCTTextAttributes *)baseTextAttributes

Libraries/Text/VirtualText/RCTVirtualTextShadowView.m

-35
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,6 @@ @implementation RCTVirtualTextShadowView {
1616
BOOL _isLayoutDirty;
1717
}
1818

19-
#pragma mark - Life Cycle
20-
21-
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)index
22-
{
23-
[super insertReactSubview:subview atIndex:index];
24-
25-
[self dirtyLayout];
26-
27-
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
28-
YGNodeSetDirtiedFunc(subview.yogaNode, RCTVirtualTextShadowViewYogaNodeDirtied);
29-
}
30-
31-
}
32-
33-
- (void)removeReactSubview:(RCTShadowView *)subview
34-
{
35-
if (![subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
36-
YGNodeSetDirtiedFunc(subview.yogaNode, NULL);
37-
}
38-
39-
[self dirtyLayout];
40-
41-
[super removeReactSubview:subview];
42-
}
43-
4419
#pragma mark - Layout
4520

4621
- (void)dirtyLayout
@@ -60,14 +35,4 @@ - (void)clearLayout
6035
_isLayoutDirty = NO;
6136
}
6237

63-
static void RCTVirtualTextShadowViewYogaNodeDirtied(YGNodeRef node)
64-
{
65-
RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node);
66-
67-
RCTVirtualTextShadowView *virtualTextShadowView =
68-
(RCTVirtualTextShadowView *)shadowView.reactSuperview;
69-
70-
[virtualTextShadowView dirtyLayout];
71-
}
72-
7338
@end

0 commit comments

Comments
 (0)