From 471ec84013b971990d29f67b04fe1e2174b541e5 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Tue, 2 Oct 2018 16:08:04 -0700 Subject: [PATCH 1/2] iOS: Support inline view truncation If text is truncated and an inline view appears after the truncation point, the user should not see the inline view. Instead, we have a bug such that the inline view is always visible at the end of the visible text. This commit fixes this by marking the inline view as hidden if it appears after the truncation point. **Before fix** Inline view is visible even though it appears after the truncation point: ![image](https://user-images.githubusercontent.com/199935/46381972-7d39d380-c65d-11e8-8354-91f65af651e4.png) **After fix** Inline view is properly truncated: ![image](https://user-images.githubusercontent.com/199935/46381918-3ba92880-c65d-11e8-8e61-1d6a1aae2c72.png) **Test Plan** Tested that the inline view is hidden if it appears after the truncation point when `numberOfLines` is 1 and 2. Similarly, verified that the inline view is visible if it appears before the truncation point. Adam Comella Microsoft Corp. --- Libraries/Text/Text/RCTTextShadowView.m | 7 +++++-- React/Modules/RCTUIManager.m | 7 +++++++ React/Views/RCTLayout.h | 4 +++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Libraries/Text/Text/RCTTextShadowView.m b/Libraries/Text/Text/RCTTextShadowView.m index 850879aa4f9ae7..7a1af566beb901 100644 --- a/Libraries/Text/Text/RCTTextShadowView.m +++ b/Libraries/Text/Text/RCTTextShadowView.m @@ -290,6 +290,9 @@ - (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext RCTRoundPixelValue(attachmentSize.width), RCTRoundPixelValue(attachmentSize.height) }}; + + NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:range.location]; + BOOL viewIsTruncated = NSIntersectionRange(range, truncatedGlyphRange).length != 0; RCTLayoutContext localLayoutContext = layoutContext; localLayoutContext.absolutePosition.x += frame.origin.x; @@ -300,9 +303,9 @@ - (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext layoutDirection:self.layoutMetrics.layoutDirection layoutContext:localLayoutContext]; - // Reinforcing a proper frame origin for the Shadow View. RCTLayoutMetrics localLayoutMetrics = shadowView.layoutMetrics; - localLayoutMetrics.frame.origin = frame.origin; + localLayoutMetrics.frame.origin = frame.origin; // Reinforcing a proper frame origin for the Shadow View. + localLayoutMetrics.isHiddenDueToClipping = viewIsTruncated; [shadowView layoutWithMetrics:localLayoutMetrics layoutContext:localLayoutContext]; } ]; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 0760c7e57869c3..d07ae359e95b72 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -488,6 +488,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * UIUserInterfaceLayoutDirection layoutDirection; BOOL isNew; BOOL parentIsNew; + BOOL isHiddenDueToClipping; } RCTFrameData; // Construct arrays then hand off to main thread @@ -505,6 +506,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * layoutMetrics.layoutDirection, shadowView.isNewView, shadowView.superview.isNewView, + layoutMetrics.isHiddenDueToClipping }; } } @@ -566,6 +568,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation; BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew; RCTLayoutAnimation *creatingLayoutAnimation = shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil; + BOOL isHiddenDueToClipping = frameData.isHiddenDueToClipping; void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; @@ -581,6 +584,10 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * if (view.reactLayoutDirection != layoutDirection) { view.reactLayoutDirection = layoutDirection; } + + if (view.isHidden != isHiddenDueToClipping) { + view.hidden = isHiddenDueToClipping; + } if (creatingLayoutAnimation) { diff --git a/React/Views/RCTLayout.h b/React/Views/RCTLayout.h index 57047b39d43edd..e232abc3a26823 100644 --- a/React/Views/RCTLayout.h +++ b/React/Views/RCTLayout.h @@ -26,6 +26,7 @@ struct RCTLayoutMetrics { UIEdgeInsets borderWidth; RCTDisplayType displayType; UIUserInterfaceLayoutDirection layoutDirection; + BOOL isHiddenDueToClipping; }; typedef struct CG_BOXABLE RCTLayoutMetrics RCTLayoutMetrics; @@ -43,7 +44,8 @@ static inline BOOL RCTLayoutMetricsEqualToLayoutMetrics(RCTLayoutMetrics a, RCTL CGRectEqualToRect(a.contentFrame, b.contentFrame) && UIEdgeInsetsEqualToEdgeInsets(a.borderWidth, b.borderWidth) && a.displayType == b.displayType && - a.layoutDirection == b.layoutDirection; + a.layoutDirection == b.layoutDirection && + a.isHiddenDueToClipping == b.isHiddenDueToClipping; } RCT_EXTERN RCTLayoutMetrics RCTLayoutMetricsFromYogaNode(YGNodeRef yogaNode); From e8500ef19b40a0836a223be387371a182d320e06 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Thu, 15 Nov 2018 14:11:20 -0800 Subject: [PATCH 2/2] Leverage existing `displayType` field rather than introducing a new `isHiddenDueToClipping` field --- Libraries/Text/Text/RCTTextShadowView.m | 4 +++- React/Modules/RCTUIManager.m | 10 +++++----- React/Views/RCTLayout.h | 4 +--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Libraries/Text/Text/RCTTextShadowView.m b/Libraries/Text/Text/RCTTextShadowView.m index 7a1af566beb901..d1baf9ccb152ec 100644 --- a/Libraries/Text/Text/RCTTextShadowView.m +++ b/Libraries/Text/Text/RCTTextShadowView.m @@ -305,7 +305,9 @@ - (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext RCTLayoutMetrics localLayoutMetrics = shadowView.layoutMetrics; localLayoutMetrics.frame.origin = frame.origin; // Reinforcing a proper frame origin for the Shadow View. - localLayoutMetrics.isHiddenDueToClipping = viewIsTruncated; + if (viewIsTruncated) { + localLayoutMetrics.displayType = RCTDisplayTypeNone; + } [shadowView layoutWithMetrics:localLayoutMetrics layoutContext:localLayoutContext]; } ]; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index d07ae359e95b72..a8960f88de2e52 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -488,7 +488,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * UIUserInterfaceLayoutDirection layoutDirection; BOOL isNew; BOOL parentIsNew; - BOOL isHiddenDueToClipping; + RCTDisplayType displayType; } RCTFrameData; // Construct arrays then hand off to main thread @@ -506,7 +506,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * layoutMetrics.layoutDirection, shadowView.isNewView, shadowView.superview.isNewView, - layoutMetrics.isHiddenDueToClipping + layoutMetrics.displayType }; } } @@ -568,7 +568,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation; BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew; RCTLayoutAnimation *creatingLayoutAnimation = shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil; - BOOL isHiddenDueToClipping = frameData.isHiddenDueToClipping; + BOOL isHidden = frameData.displayType == RCTDisplayTypeNone; void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; @@ -585,8 +585,8 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView * view.reactLayoutDirection = layoutDirection; } - if (view.isHidden != isHiddenDueToClipping) { - view.hidden = isHiddenDueToClipping; + if (view.isHidden != isHidden) { + view.hidden = isHidden; } if (creatingLayoutAnimation) { diff --git a/React/Views/RCTLayout.h b/React/Views/RCTLayout.h index e232abc3a26823..57047b39d43edd 100644 --- a/React/Views/RCTLayout.h +++ b/React/Views/RCTLayout.h @@ -26,7 +26,6 @@ struct RCTLayoutMetrics { UIEdgeInsets borderWidth; RCTDisplayType displayType; UIUserInterfaceLayoutDirection layoutDirection; - BOOL isHiddenDueToClipping; }; typedef struct CG_BOXABLE RCTLayoutMetrics RCTLayoutMetrics; @@ -44,8 +43,7 @@ static inline BOOL RCTLayoutMetricsEqualToLayoutMetrics(RCTLayoutMetrics a, RCTL CGRectEqualToRect(a.contentFrame, b.contentFrame) && UIEdgeInsetsEqualToEdgeInsets(a.borderWidth, b.borderWidth) && a.displayType == b.displayType && - a.layoutDirection == b.layoutDirection && - a.isHiddenDueToClipping == b.isHiddenDueToClipping; + a.layoutDirection == b.layoutDirection; } RCT_EXTERN RCTLayoutMetrics RCTLayoutMetricsFromYogaNode(YGNodeRef yogaNode);