Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ios): layout issue in mixed text-image with truncation scenarios #4181

Merged
merged 1 commit into from
Feb 11, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 45 additions & 14 deletions renderer/native/ios/renderer/component/text/HippyShadowText.mm
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ @implementation HippyAttributedStringStyleInfo

@interface HippyShadowText () <NSLayoutManagerDelegate>
{
BOOL _hasAttachment; // Indicates whether Text has attachment, for speeding up typesetting calculations
BOOL _isNestedText; // Indicates whether Text is nested, for speeding up typesetting calculations
BOOL _needRelayoutText; // special styles require two layouts, eg. verticalAlign etc
hippy::LayoutMeasureMode _cachedTextStorageWidthMode; // cached width mode when building text storage
Expand Down Expand Up @@ -231,19 +232,18 @@ - (void)processUpdatedPropertiesBeforeMount:(NSMutableSet<NativeRenderApplierBlo
}];
}

- (void)amendLayoutBeforeMount:(NSMutableSet<NativeRenderApplierBlock> *)blocks {
- (void)updateAttachmentsFrame:(NSMutableSet<NativeRenderApplierBlock> * _Nonnull)blocks {
@try {
UIEdgeInsets padding = self.paddingAsInsets;
CGFloat width = self.frame.size.width - (padding.left + padding.right);
NSTextStorage *textStorage = [self buildTextStorageForWidth:width widthMode:hippy::LayoutMeasureMode::Exactly];
CGRect textFrame = [self calculateTextFrame:textStorage];

NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
[textStorage enumerateAttribute:HippyShadowViewAttributeName inRange:characterRange options:0 usingBlock:^(
HippyShadowView *child, NSRange range, __unused BOOL *_) {
[textStorage enumerateAttribute:HippyShadowViewAttributeName
inRange:NSMakeRange(0, textStorage.length)
options:0
usingBlock:^(HippyShadowView *child, NSRange range, __unused BOOL *_) {
if (child) {
float width = child.width, height = child.height;
if (isnan(width) || isnan(height)) {
Expand All @@ -252,15 +252,33 @@ - (void)amendLayoutBeforeMount:(NSMutableSet<NativeRenderApplierBlock> *)blocks

// Use line fragment's rect instead of glyph rect for calculation,
// since we have changed the baselineOffset.
CGRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:range.location
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:nil];
CGRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphRange.location
effectiveRange:nil
withoutAdditionalLayout:YES];
CGPoint location = [layoutManager locationForGlyphAtIndex:range.location];
CGPoint location = [layoutManager locationForGlyphAtIndex:glyphRange.location];
CGFloat roundedHeight = HippyRoundPixelValue(height);
CGFloat roundedWidth = HippyRoundPixelValue(width);

// take margin into account
// FIXME: margin currently not working, may have some bug in layout process
CGSize attachmentSize = [layoutManager attachmentSizeForGlyphAtIndex:glyphRange.location];
NSRange truncatedRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.location];
BOOL isTruncated = NSLocationInRange(glyphRange.location, truncatedRange);
// if truncated or out of the display area, then we need to hide the attach view
// {-1, -1} means attachment view has been truncated, ref: attachmentSizeForGlyphAtIndex api
if ((isTruncated && CGSizeEqualToSize({-1, -1}, attachmentSize))
|| location.x >= CGRectGetMaxX(lineRect) || location.y >= CGRectGetMaxY(lineRect)) {
[blocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry,
UIView * _Nullable lazyCreatedView) {
HippyView *view = (HippyView *)(lazyCreatedView ?: viewRegistry[child.hippyTag]);
if (!view) { return; }
if (!HippyCGRectNearlyEqual(view.frame, CGRectZero)) {
[view setFrame:CGRectZero];
}
}];
return;
}

// TODO: support margin for attachment
float left = 0;
float top = 0;
float marginV = child.nodeLayoutResult.marginTop + child.nodeLayoutResult.marginBottom;
Expand Down Expand Up @@ -298,10 +316,14 @@ - (void)amendLayoutBeforeMount:(NSMutableSet<NativeRenderApplierBlock> *)blocks
CGRect childFrameToSet = CGRectMake(textFrame.origin.x + location.x + left,
textFrame.origin.y + positionY + top,
roundedWidth, roundedHeight);
CGRect childFrame = child.frame;
if (!HippyCGRectNearlyEqual(childFrame, childFrameToSet)) {
[child setLayoutFrame:childFrameToSet dirtyPropagation:NO];
}
[blocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry,
UIView * _Nullable lazyCreatedView) {
HippyView *view = (HippyView *)(lazyCreatedView ?: viewRegistry[child.hippyTag]);
if (!view) { return; }
if (!HippyCGRectNearlyEqual(view.frame, childFrameToSet)) {
[view setFrame:childFrameToSet];
}
}];
}
}];

Expand All @@ -318,6 +340,13 @@ - (void)amendLayoutBeforeMount:(NSMutableSet<NativeRenderApplierBlock> *)blocks
} @catch (NSException *exception) {
HippyLogError(@"Exception while doing %s: %@, %@", __func__, exception.description, self);
}
}

- (void)amendLayoutBeforeMount:(NSMutableSet<NativeRenderApplierBlock> *)blocks {
if (_hasAttachment) {
[self updateAttachmentsFrame:blocks];
}

[self processUpdatedPropertiesBeforeMount:blocks];
}

Expand Down Expand Up @@ -493,6 +522,7 @@ - (NSAttributedString *)_attributedStringWithStyleInfo:(HippyAttributedStringSty

CGFloat heightOfTallestSubview = 0.0;
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:self.text ?: @""];
_hasAttachment = NO;
for (HippyShadowView *child in [self hippySubviews]) {
if ([child isKindOfClass:[HippyShadowText class]]) {
HippyShadowText *childShadowText = (HippyShadowText *)child;
Expand Down Expand Up @@ -561,6 +591,7 @@ - (NSAttributedString *)_attributedStringWithStyleInfo:(HippyAttributedStringSty
if (height > heightOfTallestSubview) {
heightOfTallestSubview = height;
}
_hasAttachment = YES;
// Don't call setTextComputed on this child. HippyTextManager takes care of
// processing inline UIViews.
}
Expand Down
Loading