mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
fix(ios): keep recycled text views at their container width
Apply the minimal candidate from #6364 and cover recycled geometry in the native probe. Document that the probe does not reproduce mounted Fabric clipping. Co-authored-by: Oxxx <103127477+omcdowell@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,11 @@ bash packages/app/e2e/mobile/native-text-reflow/ios.sh
|
||||
The probe constructs the installed `RNUITextView`, fills its real UIKit text view,
|
||||
and resizes its content container from 500 to 240 and back to 500 points. It
|
||||
asserts the inner frame follows both widths and the same selection survives.
|
||||
It then recycles the view the way Fabric's pool does and remounts it 30 points
|
||||
wider and at the same size; the inner frame must still match the container.
|
||||
A zeroed frame on recycle comes back 30 or 0 points wide. This assertion checks
|
||||
the intermediate recycled geometry; it does not reproduce visible clipping in a
|
||||
mounted Fabric paragraph or exercise the subsequent `drawRect` call.
|
||||
It uses public UIView/UITextView operations, without mocking React Native.
|
||||
LLDB detaches after the probe. An assertion failure or evaluation error fails the
|
||||
shell command; the log distinguishes them.
|
||||
|
||||
@@ -39,14 +39,28 @@ def __lldb_init_module(debugger, _internal_dict):
|
||||
container.frame = frame;
|
||||
[container layoutIfNeeded];
|
||||
CGFloat wide = text.frame.size.width;
|
||||
auto keptSelection = text.selectedRange;
|
||||
// Fabric recycles the view: the container keeps its size in the pool,
|
||||
// and the next paragraph mounts it 30 points wider (a list-item paragraph
|
||||
// reused by a full-width one) or at the same size.
|
||||
[host performSelector:@selector(prepareForRecycle)];
|
||||
frame.size.width = 530;
|
||||
container.frame = frame;
|
||||
[container layoutIfNeeded];
|
||||
CGFloat reusedWider = text.frame.size.width;
|
||||
[host performSelector:@selector(prepareForRecycle)];
|
||||
container.frame = frame;
|
||||
[container layoutIfNeeded];
|
||||
CGFloat reusedSame = text.frame.size.width;
|
||||
BOOL ok = text != nil && narrow == 240 && wide == 500 &&
|
||||
selection.location == text.selectedRange.location &&
|
||||
selection.length == text.selectedRange.length &&
|
||||
reusedWider == 530 && reusedSame == 530 &&
|
||||
selection.location == keptSelection.location &&
|
||||
selection.length == keptSelection.length &&
|
||||
selection.location == 12 && selection.length == 9;
|
||||
[NSString stringWithFormat:@"NATIVE_REFLOW_%@ narrow=%.0f wide=%.0f selection=%lu:%lu",
|
||||
ok ? @"PASS" : @"FAIL", narrow, wide,
|
||||
(unsigned long)text.selectedRange.location,
|
||||
(unsigned long)text.selectedRange.length];
|
||||
[NSString stringWithFormat:@"NATIVE_REFLOW_%@ narrow=%.0f wide=%.0f recycled=%.0f,%.0f selection=%lu:%lu",
|
||||
ok ? @"PASS" : @"FAIL", narrow, wide, reusedWider, reusedSame,
|
||||
(unsigned long)keptSelection.location,
|
||||
(unsigned long)keptSelection.length];
|
||||
})''',
|
||||
options,
|
||||
)
|
||||
|
||||
@@ -15,4 +15,4 @@ xcrun lldb --batch -p "$PASEO_NATIVE_TEXT_REFLOW_PID" \
|
||||
-o "process detach" -o quit > "$PASEO_NATIVE_TEXT_REFLOW_LOG" 2>&1
|
||||
|
||||
grep '^NATIVE_REFLOW_' "$PASEO_NATIVE_TEXT_REFLOW_LOG"
|
||||
grep -q '^NATIVE_REFLOW_PASS narrow=240 wide=500 selection=12:9$' "$PASEO_NATIVE_TEXT_REFLOW_LOG"
|
||||
grep -q '^NATIVE_REFLOW_PASS narrow=240 wide=500 recycled=530,530 selection=12:9$' "$PASEO_NATIVE_TEXT_REFLOW_LOG"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/node_modules/react-native-uitextview/ios/RNUITextView.mm b/node_modules/react-native-uitextview/ios/RNUITextView.mm
|
||||
index d0c31ed..df8a233 100644
|
||||
index d0c31ed..d1605ea 100644
|
||||
--- a/node_modules/react-native-uitextview/ios/RNUITextView.mm
|
||||
+++ b/node_modules/react-native-uitextview/ios/RNUITextView.mm
|
||||
@@ -22,6 +22,7 @@ using namespace facebook::react;
|
||||
@@ -20,9 +20,17 @@ index d0c31ed..df8a233 100644
|
||||
|
||||
const auto longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleLongPressIfNecessary:)];
|
||||
@@ -94,16 +96,15 @@ using namespace facebook::react;
|
||||
// Reset the frame to zero so that when it properly lays out on the next use
|
||||
_textView.frame = CGRectZero;
|
||||
@@ -91,19 +93,21 @@ using namespace facebook::react;
|
||||
[super prepareForRecycle];
|
||||
_state.reset();
|
||||
|
||||
- // Reset the frame to zero so that when it properly lays out on the next use
|
||||
- _textView.frame = CGRectZero;
|
||||
+ // Keep the text view on the content container's bounds. The container keeps
|
||||
+ // its size while the view waits in the recycle pool, and autoresizing only
|
||||
+ // applies the size delta on reuse, so a zeroed text view would come back as
|
||||
+ // wide as that delta (zero, or a two-word column).
|
||||
+ _textView.frame = _view.bounds;
|
||||
_textView.attributedText = nil;
|
||||
+ _lastTextLayoutFrame = CGRectZero;
|
||||
}
|
||||
@@ -41,7 +49,7 @@ index d0c31ed..df8a233 100644
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
@@ -124,8 +125,9 @@ using namespace facebook::react;
|
||||
@@ -124,10 +128,16 @@ using namespace facebook::react;
|
||||
// entirely when nothing actually changed so a JS-side state update made in
|
||||
// response to onSelectionChange doesn't deselect what the user is selecting.
|
||||
const BOOL textChanged = ![_textView.attributedText isEqualToAttributedString:convertedAttrString];
|
||||
@@ -52,17 +60,25 @@ index d0c31ed..df8a233 100644
|
||||
+ if (!textChanged && !frameChanged && !layoutChanged) {
|
||||
return;
|
||||
}
|
||||
+ // Size the text view before handing it the text, so UIKit lays the text out
|
||||
+ // at the container width instead of a stale width.
|
||||
+ if (frameChanged) {
|
||||
+ _textView.frame = _view.bounds;
|
||||
+ }
|
||||
if (textChanged) {
|
||||
@@ -142,7 +144,7 @@ using namespace facebook::react;
|
||||
// Reassigning attributedText clears any active selection. Save it and
|
||||
// restore after, while suppressing the synthetic textViewDidChangeSelection
|
||||
@@ -141,9 +151,6 @@ using namespace facebook::react;
|
||||
}
|
||||
_suppressSelectionChange = NO;
|
||||
}
|
||||
if (frameChanged) {
|
||||
- if (frameChanged) {
|
||||
- _textView.frame = _view.frame;
|
||||
+ _textView.frame = _view.bounds;
|
||||
}
|
||||
- }
|
||||
|
||||
__block std::vector<std::string> lines;
|
||||
@@ -162,6 +164,7 @@ using namespace facebook::react;
|
||||
const int maxLines = props.numberOfLines;
|
||||
@@ -162,6 +169,7 @@ using namespace facebook::react;
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user