Skip to content

Commit 8870628

Browse files
committed
[Layout] Add RTL support to LayoutSpecs (#1983)
* [Layout] Add RTL support to LayoutSpecs This is largely a slight update for #1805. If RTL is enabled, `calculateLayoutLayoutSpec:` will flip the origin of all sublayouts. The new part of the diff is that ASBatchFetching now supports proper fetching on RTL horizontal scrollViews. * Fix build and add RTL batch fetching tests
1 parent de115e2 commit 8870628

7 files changed

Lines changed: 121 additions & 21 deletions

Source/ASDisplayNode+LayoutSpec.mm

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ - (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize
107107
}
108108
layout = [layout filteredNodeLayoutTree];
109109

110+
// Flip layout if layout should be rendered right-to-left
111+
BOOL shouldRenderRTLLayout = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:_semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
112+
if (shouldRenderRTLLayout) {
113+
for (ASLayout *sublayout in layout.sublayouts) {
114+
switch (_semanticContentAttribute) {
115+
case UISemanticContentAttributeUnspecified:
116+
case UISemanticContentAttributeForceRightToLeft: {
117+
// Flip
118+
CGPoint flippedPosition = CGPointMake(layout.size.width - CGRectGetWidth(sublayout.frame) - sublayout.position.x, sublayout.position.y);
119+
sublayout.position = flippedPosition;
120+
}
121+
case UISemanticContentAttributePlayback:
122+
case UISemanticContentAttributeForceLeftToRight:
123+
case UISemanticContentAttributeSpatial:
124+
// Don't flip
125+
break;
126+
}
127+
}
128+
}
129+
130+
110131
return layout;
111132
}
112133

Source/ASDisplayNode.mm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,21 @@ - (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType
16251625
});
16261626
}
16271627

1628+
- (void)updateSemanticContentAttributeWithAttribute:(UISemanticContentAttribute)attribute
1629+
{
1630+
__instanceLock__.lock();
1631+
UISemanticContentAttribute oldAttribute = _semanticContentAttribute;
1632+
_semanticContentAttribute = attribute;
1633+
__instanceLock__.unlock();
1634+
1635+
ASPerformBlockOnMainThread(^{
1636+
// If the value has changed we should attempt to relayout.
1637+
if (attribute != oldAttribute) {
1638+
[self setNeedsLayout];
1639+
}
1640+
});
1641+
}
1642+
16281643
- (void)recursivelySetDisplaySuspended:(BOOL)flag
16291644
{
16301645
_recursivelySetDisplaySuspended(self, nil, flag);

Source/Private/ASBatchFetching.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ ASDK_EXTERN BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
6767
CGPoint targetOffset,
6868
CGFloat leadingScreens,
6969
BOOL visible,
70+
BOOL shouldRenderRTLLayout,
7071
CGPoint velocity,
7172
_Nullable id<ASBatchFetchingDelegate> delegate);
7273

Source/Private/ASBatchFetching.mm

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView<ASBatchFetchingScrollVi
2929
CGFloat leadingScreens = scrollView.leadingScreensForBatching;
3030
id<ASBatchFetchingDelegate> delegate = scrollView.batchFetchingDelegate;
3131
BOOL visible = (scrollView.window != nil);
32-
return ASDisplayShouldFetchBatchForContext(context, scrollDirection, scrollableDirections, bounds, contentSize, contentOffset, leadingScreens, visible, velocity, delegate);
32+
BOOL shouldRenderRTLLayout = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:scrollView.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft;
33+
return ASDisplayShouldFetchBatchForContext(context, scrollDirection, scrollableDirections, bounds, contentSize, contentOffset, leadingScreens, visible, shouldRenderRTLLayout, velocity, delegate);
3334
}
3435

3536
BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
@@ -40,6 +41,7 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
4041
CGPoint targetOffset,
4142
CGFloat leadingScreens,
4243
BOOL visible,
44+
BOOL shouldRenderRTLLayout,
4345
CGPoint velocity,
4446
id<ASBatchFetchingDelegate> delegate)
4547
{
@@ -79,13 +81,18 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
7981
}
8082

8183
// If they are scrolling toward the head of content, don't batch fetch.
82-
BOOL isScrollingTowardHead = (ASScrollDirectionContainsUp(scrollDirection) || ASScrollDirectionContainsLeft(scrollDirection));
84+
BOOL isScrollingTowardHead = (ASScrollDirectionContainsUp(scrollDirection) || (shouldRenderRTLLayout ? ASScrollDirectionContainsRight(scrollDirection) : ASScrollDirectionContainsLeft(scrollDirection)));
8385
if (isScrollingTowardHead) {
8486
return NO;
8587
}
8688

8789
CGFloat triggerDistance = viewLength * leadingScreens;
8890
CGFloat remainingDistance = contentLength - viewLength - offset;
91+
if (shouldRenderRTLLayout && ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) {
92+
remainingDistance = offset;
93+
} else {
94+
remainingDistance = contentLength - viewLength - offset;
95+
}
8996
BOOL result = remainingDistance <= triggerDistance;
9097

9198
if (delegate != nil && velocityLength > 0.0) {

Source/Private/ASDisplayNode+UIViewBridge.mm

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -944,14 +944,15 @@ - (void)setEdgeAntialiasingMask:(CAEdgeAntialiasingMask)edgeAntialiasingMask
944944

945945
- (UISemanticContentAttribute)semanticContentAttribute
946946
{
947-
_bridge_prologue_read;
948-
return _getFromViewOnly(semanticContentAttribute);
947+
AS::MutexLocker l(__instanceLock__);
948+
return _semanticContentAttribute;
949949
}
950950

951951
- (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute
952952
{
953-
_bridge_prologue_write;
953+
AS::MutexLocker l(__instanceLock__);
954954
_setToViewOnly(semanticContentAttribute, semanticContentAttribute);
955+
_semanticContentAttribute = semanticContentAttribute;
955956
#if YOGA
956957
[self semanticContentAttributeDidChange:semanticContentAttribute];
957958
#endif

Source/Private/ASDisplayNodeInternal.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ static constexpr CACornerMask kASCACornerAllCorners =
258258
// These properties are used on iOS 10 and lower, where safe area is not supported by UIKit.
259259
UIEdgeInsets _fallbackSafeAreaInsets;
260260

261-
261+
// Right-to-Left layout support
262+
UISemanticContentAttribute _semanticContentAttribute;
262263

263264
#pragma mark - ASDisplayNode (Debugging)
264265
ASLayout *_unflattenedLayout;
@@ -332,6 +333,9 @@ static constexpr CACornerMask kASCACornerAllCorners =
332333
cornerRadius:(CGFloat)newCornerRadius
333334
maskedCorners:(CACornerMask)newMaskedCorners;
334335

336+
/// Update the Semantic Content Attribute. Trigger layout if this value has changed.
337+
- (void)updateSemanticContentAttributeWithAttribute:(UISemanticContentAttribute)attribute;
338+
335339
/// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses.
336340
- (instancetype)initWithViewClass:(Class)viewClass;
337341

0 commit comments

Comments
 (0)