From 5b9258db962616731fefa43118198beef90afa23 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 11 Feb 2025 20:21:32 +0800 Subject: [PATCH] fix(ios): layout issue in mixed text-image with truncation scenarios --- .../component/text/HippyShadowText.mm | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/renderer/native/ios/renderer/component/text/HippyShadowText.mm b/renderer/native/ios/renderer/component/text/HippyShadowText.mm index a9e404624b8..5838ea6228e 100644 --- a/renderer/native/ios/renderer/component/text/HippyShadowText.mm +++ b/renderer/native/ios/renderer/component/text/HippyShadowText.mm @@ -87,6 +87,7 @@ @implementation HippyAttributedStringStyleInfo @interface HippyShadowText () { + 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 @@ -231,7 +232,7 @@ - (void)processUpdatedPropertiesBeforeMount:(NSMutableSet *)blocks { +- (void)updateAttachmentsFrame:(NSMutableSet * _Nonnull)blocks { @try { UIEdgeInsets padding = self.paddingAsInsets; CGFloat width = self.frame.size.width - (padding.left + padding.right); @@ -239,11 +240,10 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks 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)) { @@ -252,15 +252,33 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)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 *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; @@ -298,10 +316,14 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)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 *viewRegistry, + UIView * _Nullable lazyCreatedView) { + HippyView *view = (HippyView *)(lazyCreatedView ?: viewRegistry[child.hippyTag]); + if (!view) { return; } + if (!HippyCGRectNearlyEqual(view.frame, childFrameToSet)) { + [view setFrame:childFrameToSet]; + } + }]; } }]; @@ -318,6 +340,13 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks } @catch (NSException *exception) { HippyLogError(@"Exception while doing %s: %@, %@", __func__, exception.description, self); } +} + +- (void)amendLayoutBeforeMount:(NSMutableSet *)blocks { + if (_hasAttachment) { + [self updateAttachmentsFrame:blocks]; + } + [self processUpdatedPropertiesBeforeMount:blocks]; } @@ -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; @@ -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. }