Reimagining of RCTShadowView layout API

Summary:
This is reimagining of interoperability layer between Yoga and ShadowViews (at least in Yoga -> RN part).
Goals:
 * Make it clear and easy.
 * Make clear separation between "what layout what", now parent always layout children, noone layout itself.
 * Make possible to interleave Yoga layout with custom imperative layout (may be used in SafeAreaView, Text, Modal, InputAccessoryView and so on).

Reviewed By: mmmulani

Differential Revision: D6863654

fbshipit-source-id: 5a6a933874f121d46f744aab99a31ae42ddd4a1b
This commit is contained in:
Valentin Shergin 2018-02-12 00:04:16 -08:00 коммит произвёл Facebook Github Bot
Родитель 47b36d3ff0
Коммит f91f7d91a1
17 изменённых файлов: 471 добавлений и 311 удалений

Просмотреть файл

@ -235,40 +235,24 @@
return textStorage;
}
- (void)applyLayoutNode:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
layoutContext:(RCTLayoutContext)layoutContext
{
if (YGNodeGetHasNewLayout(self.yogaNode)) {
// If the view got new layout, we have to redraw it because `contentFrame`
// and sizes of embedded views may change.
// If the view got new `contentFrame`, we have to redraw it because
// and sizes of embedded views may change.
if (!CGRectEqualToRect(self.layoutMetrics.contentFrame, layoutMetrics.contentFrame)) {
_needsUpdateView = YES;
}
[super applyLayoutNode:node
viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition];
}
- (void)applyLayoutWithFrame:(CGRect)frame
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
viewsWithUpdatedLayout:(NSMutableSet<RCTShadowView *> *)viewsWithUpdatedLayout
absolutePosition:(CGPoint)absolutePosition
{
if (self.textAttributes.layoutDirection != layoutDirection) {
self.textAttributes.layoutDirection = layoutDirection;
if (self.textAttributes.layoutDirection != layoutMetrics.layoutDirection) {
self.textAttributes.layoutDirection = layoutMetrics.layoutDirection;
[self invalidateCache];
}
[super applyLayoutWithFrame:frame
layoutDirection:layoutDirection
viewsWithUpdatedLayout:viewsWithUpdatedLayout
absolutePosition:absolutePosition];
[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext];
}
- (void)applyLayoutToChildren:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext
{
NSTextStorage *textStorage =
[self textStorageAndLayoutManagerThatFitsSize:self.availableSize
@ -280,9 +264,9 @@
actualGlyphRange:NULL];
[textStorage enumerateAttribute:RCTBaseTextShadowViewEmbeddedShadowViewAttributeName
inRange:characterRange
options:0
usingBlock:
inRange:characterRange
options:0
usingBlock:
^(RCTShadowView *shadowView, NSRange range, BOOL *stop) {
if (!shadowView) {
return;
@ -306,18 +290,14 @@
RCTRoundPixelValue(attachmentSize.height)
}};
UIUserInterfaceLayoutDirection layoutDirection = self.textAttributes.layoutDirection;
RCTLayoutContext localLayoutContext = layoutContext;
localLayoutContext.absolutePosition.x += frame.origin.x;
localLayoutContext.absolutePosition.y += frame.origin.y;
YGNodeCalculateLayout(
shadowView.yogaNode,
frame.size.width,
frame.size.height,
layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight ? YGDirectionLTR : YGDirectionRTL);
[shadowView applyLayoutWithFrame:frame
layoutDirection:layoutDirection
viewsWithUpdatedLayout:viewsWithNewFrame
absolutePosition:absolutePosition];
[shadowView layoutWithMinimumSize:frame.size
maximumSize:frame.size
layoutDirection:self.layoutMetrics.layoutDirection
layoutContext:localLayoutContext];
}
];
}

Просмотреть файл

@ -47,6 +47,11 @@
return YES;
}
- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext
{
// Do nothing.
}
- (void)setLocalData:(NSObject *)localData
{
NSAttributedString *attributedText = (NSAttributedString *)localData;
@ -73,7 +78,7 @@
return;
}
CGSize maximumSize = self.frame.size;
CGSize maximumSize = self.layoutMetrics.frame.size;
if (_maximumNumberOfLines == 1) {
maximumSize.width = CGFLOAT_MAX;

Просмотреть файл

@ -889,13 +889,6 @@
name = Products;
sourceTree = "<group>";
};
19BA89021F8439A700741C5A /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
272E6B3A1BEA846C001FCF37 /* NativeExampleViews */ = {
isa = PBXGroup;
children = (
@ -1157,6 +1150,9 @@
004D289D1AAF61C70097A701 = {
CreatedOnToolsVersion = 6.1.1;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = V9WTTPBFK9;
};
143BC5941B21E3E100462512 = {
CreatedOnToolsVersion = 6.3.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
@ -1854,6 +1850,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = V9WTTPBFK9;
INFOPLIST_FILE = "$(SRCROOT)/RNTester/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LIBRARY_SEARCH_PATHS = "$(inherited)";

Просмотреть файл

@ -88,7 +88,7 @@
[self.parentView insertReactSubview:mainView atIndex:1];
[self.parentView insertReactSubview:footerView atIndex:2];
[self.parentView collectViewsWithUpdatedFrames];
[self.parentView layoutWithAffectedShadowViews:[NSHashTable weakObjectsHashTable]];
XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440)));
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
@ -180,7 +180,7 @@
RCTShadowView *view = [self _shadowViewWithConfig:configBlock];
[self.parentView insertReactSubview:view atIndex:0];
view.intrinsicContentSize = contentSize;
[self.parentView collectViewsWithUpdatedFrames];
[self.parentView layoutWithAffectedShadowViews:[NSHashTable weakObjectsHashTable]];
CGRect actualRect = [view measureLayoutRelativeToAncestor:self.parentView];
XCTAssertTrue(CGRectEqualToRect(expectedRect, actualRect),
@"Expected layout to be %@, got %@",

Просмотреть файл

@ -29,10 +29,6 @@
*/
@property (nonatomic, assign) YGDirection baseDirection;
/**
* Calculate all views whose frame needs updating after layout has been calculated.
* Returns a set contains the shadowviews that need updating.
*/
- (NSSet<RCTShadowView *> *)collectViewsWithUpdatedFrames;
- (void)layoutWithAffectedShadowViews:(NSHashTable<RCTShadowView *> *)affectedShadowViews;
@end

Просмотреть файл

@ -43,37 +43,26 @@
}
}
- (void)calculateLayoutWithMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
- (void)layoutWithAffectedShadowViews:(NSHashTable<RCTShadowView *> *)affectedShadowViews
{
YGNodeRef yogaNode = self.yogaNode;
NSHashTable<NSString *> *other = [NSHashTable new];
YGNodeStyleSetMinWidth(yogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.width));
YGNodeStyleSetMinHeight(yogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.height));
RCTLayoutContext layoutContext = {};
layoutContext.absolutePosition = CGPointZero;
layoutContext.affectedShadowViews = affectedShadowViews;
layoutContext.other = other;
YGNodeCalculateLayout(
self.yogaNode,
RCTYogaFloatFromCoreGraphicsFloat(maximumSize.width),
RCTYogaFloatFromCoreGraphicsFloat(maximumSize.height),
_baseDirection
);
}
[self layoutWithMinimumSize:_minimumSize
maximumSize:_maximumSize
layoutDirection:RCTUIKitLayoutDirectionFromYogaLayoutDirection(_baseDirection)
layoutContext:layoutContext];
- (NSSet<RCTShadowView *> *)collectViewsWithUpdatedFrames
{
[self calculateLayoutWithMinimumSize:_minimumSize
maximumSize:_maximumSize];
NSMutableSet<RCTShadowView *> *viewsWithNewFrame = [NSMutableSet set];
[self applyLayoutNode:self.yogaNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];
self.intrinsicSize = self.frame.size;
self.intrinsicSize = self.layoutMetrics.frame.size;
if (_isRendered && !_isLaidOut) {
[_delegate rootShadowViewDidStartLayingOut:self];
_isLaidOut = YES;
}
return viewsWithNewFrame;
}
- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize

Просмотреть файл

@ -479,14 +479,10 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
{
RCTAssertUIManagerQueue();
// This is nuanced. In the JS thread, we create a new update buffer
// `frameTags`/`frames` that is created/mutated in the JS thread. We access
// these structures in the UI-thread block. `NSMutableArray` is not thread
// safe so we rely on the fact that we never mutate it after it's passed to
// the main thread.
NSSet<RCTShadowView *> *viewsWithNewFrames = [rootShadowView collectViewsWithUpdatedFrames];
NSHashTable<RCTShadowView *> *affectedShadowViews = [NSHashTable weakObjectsHashTable];
[rootShadowView layoutWithAffectedShadowViews:affectedShadowViews];
if (!viewsWithNewFrames.count) {
if (!affectedShadowViews.count) {
// no frame change results in no UI update block
return nil;
}
@ -499,24 +495,25 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
} RCTFrameData;
// Construct arrays then hand off to main thread
NSUInteger count = viewsWithNewFrames.count;
NSUInteger count = affectedShadowViews.count;
NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
{
NSUInteger index = 0;
RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
for (RCTShadowView *shadowView in viewsWithNewFrames) {
for (RCTShadowView *shadowView in affectedShadowViews) {
reactTags[index] = shadowView.reactTag;
RCTLayoutMetrics layoutMetrics = shadowView.layoutMetrics;
frameDataArray[index++] = (RCTFrameData){
shadowView.frame,
shadowView.layoutDirection,
layoutMetrics.frame,
layoutMetrics.layoutDirection,
shadowView.isNewView,
shadowView.superview.isNewView,
};
}
}
for (RCTShadowView *shadowView in viewsWithNewFrames) {
for (RCTShadowView *shadowView in affectedShadowViews) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
@ -524,7 +521,7 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
NSNumber *reactTag = shadowView.reactTag;
if (shadowView.onLayout) {
CGRect frame = shadowView.frame;
CGRect frame = shadowView.layoutMetrics.frame;
shadowView.onLayout(@{
@"layout": @{
@"x": @(frame.origin.x),
@ -539,7 +536,7 @@ static NSDictionary *deviceOrientationEventBody(UIDeviceOrientation orientation)
RCTIsReactRootView(reactTag) &&
[shadowView isKindOfClass:[RCTRootShadowView class]]
) {
CGSize contentSize = shadowView.frame.size;
CGSize contentSize = shadowView.layoutMetrics.frame.size;
RCTExecuteOnMainQueue(^{
UIView *view = self->_viewRegistry[reactTag];

Просмотреть файл

@ -978,6 +978,12 @@
590D7BFE1EBD458B00D8A370 /* RCTShadowView+Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = 590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */; };
590D7BFF1EBD458B00D8A370 /* RCTShadowView+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */; };
590D7C001EBD458B00D8A370 /* RCTShadowView+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */; };
591F78DA202ADB22004A668C /* RCTLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 591F78D8202ADB21004A668C /* RCTLayout.m */; };
591F78DB202ADB22004A668C /* RCTLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 591F78D8202ADB21004A668C /* RCTLayout.m */; };
591F78DC202ADB22004A668C /* RCTLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 591F78D9202ADB22004A668C /* RCTLayout.h */; };
591F78DD202ADB22004A668C /* RCTLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 591F78D9202ADB22004A668C /* RCTLayout.h */; };
591F78DE202ADB8F004A668C /* RCTLayout.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 591F78D9202ADB22004A668C /* RCTLayout.h */; };
591F78DF202ADB97004A668C /* RCTLayout.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 591F78D9202ADB22004A668C /* RCTLayout.h */; };
5925356A20084D0600DD584B /* RCTSurfaceSizeMeasureMode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5925356920084D0600DD584B /* RCTSurfaceSizeMeasureMode.mm */; };
5925356B20084D0600DD584B /* RCTSurfaceSizeMeasureMode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5925356920084D0600DD584B /* RCTSurfaceSizeMeasureMode.mm */; };
59283CA01FD67321000EAAB9 /* RCTSurfaceStage.m in Sources */ = {isa = PBXBuildFile; fileRef = 59283C9F1FD67320000EAAB9 /* RCTSurfaceStage.m */; };
@ -1347,6 +1353,7 @@
dstPath = include/React;
dstSubfolderSpec = 16;
files = (
591F78DF202ADB97004A668C /* RCTLayout.h in Copy Headers */,
59EDBCC31FDF4E55003573DE /* RCTScrollableProtocol.h in Copy Headers */,
59EDBCC41FDF4E55003573DE /* (null) in Copy Headers */,
59EDBCC51FDF4E55003573DE /* RCTScrollContentView.h in Copy Headers */,
@ -1574,6 +1581,7 @@
dstPath = include/React;
dstSubfolderSpec = 16;
files = (
591F78DE202ADB8F004A668C /* RCTLayout.h in Copy Headers */,
59EDBCBD1FDF4E43003573DE /* RCTScrollableProtocol.h in Copy Headers */,
59EDBCBE1FDF4E43003573DE /* (null) in Copy Headers */,
59EDBCBF1FDF4E43003573DE /* RCTScrollContentView.h in Copy Headers */,
@ -2156,6 +2164,8 @@
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTDatePickerManager.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTShadowView+Layout.h"; sourceTree = "<group>"; };
590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTShadowView+Layout.m"; sourceTree = "<group>"; };
591F78D8202ADB21004A668C /* RCTLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLayout.m; sourceTree = "<group>"; };
591F78D9202ADB22004A668C /* RCTLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLayout.h; sourceTree = "<group>"; };
5925356920084D0600DD584B /* RCTSurfaceSizeMeasureMode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTSurfaceSizeMeasureMode.mm; sourceTree = "<group>"; };
59283C9F1FD67320000EAAB9 /* RCTSurfaceStage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSurfaceStage.m; sourceTree = "<group>"; };
594F0A2F1FD23228007FBE96 /* RCTSurfaceHostingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSurfaceHostingView.h; sourceTree = "<group>"; };
@ -2536,6 +2546,8 @@
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
3D37B5801D522B190042D5B5 /* RCTFont.h */,
3D37B5811D522B190042D5B5 /* RCTFont.mm */,
591F78D9202ADB22004A668C /* RCTLayout.h */,
591F78D8202ADB21004A668C /* RCTLayout.m */,
66CD94AD1F1045E700CB3C7C /* RCTMaskedView.h */,
66CD94AE1F1045E700CB3C7C /* RCTMaskedView.m */,
66CD94AF1F1045E700CB3C7C /* RCTMaskedViewManager.h */,
@ -3151,6 +3163,7 @@
3D302F4A1DF828F800D6DDAE /* RCTRootViewInternal.h in Headers */,
59EDBCB21FDF4E0C003573DE /* (null) in Headers */,
3D302F4B1DF828F800D6DDAE /* RCTTouchEvent.h in Headers */,
591F78DD202ADB22004A668C /* RCTLayout.h in Headers */,
59D031F21F8353D3008361F0 /* RCTSafeAreaView.h in Headers */,
3D302F4C1DF828F800D6DDAE /* RCTTouchHandler.h in Headers */,
3D302F4D1DF828F800D6DDAE /* RCTURLRequestDelegate.h in Headers */,
@ -3511,6 +3524,7 @@
C60128AB1F3D1258009DF9FF /* RCTCxxConvert.h in Headers */,
59EDBCAD1FDF4E0C003573DE /* RCTScrollContentView.h in Headers */,
59EDBCA71FDF4E0C003573DE /* RCTScrollableProtocol.h in Headers */,
591F78DC202ADB22004A668C /* RCTLayout.h in Headers */,
3D80DA631DF820620028D040 /* RCTBorderDrawing.h in Headers */,
3D80DA641DF820620028D040 /* RCTBorderStyle.h in Headers */,
3D80DA651DF820620028D040 /* RCTComponent.h in Headers */,
@ -4184,6 +4198,7 @@
59D031F41F8353D3008361F0 /* RCTSafeAreaView.m in Sources */,
2D3B5E941D9B087900451313 /* RCTBundleURLProvider.m in Sources */,
2D3B5EB81D9B091B00451313 /* RCTSourceCode.m in Sources */,
591F78DB202ADB22004A668C /* RCTLayout.m in Sources */,
2D3B5EB51D9B091100451313 /* RCTDevMenu.m in Sources */,
59EDBCAC1FDF4E0C003573DE /* (null) in Sources */,
2D3B5EBD1D9B092A00451313 /* RCTTiming.m in Sources */,
@ -4452,6 +4467,7 @@
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
59A7B9FE1E577DBF0068EDBF /* RCTRootContentView.m in Sources */,
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
591F78DA202ADB22004A668C /* RCTLayout.m in Sources */,
FEFAAC9E1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.m in Sources */,
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
CF2731C11E7B8DE40044CA4F /* RCTDeviceInfo.m in Sources */,

77
React/Views/RCTLayout.h Normal file
Просмотреть файл

@ -0,0 +1,77 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
#import <yoga/Yoga.h>
NS_ASSUME_NONNULL_BEGIN
@class RCTShadowView;
typedef NS_ENUM(NSInteger, RCTDisplayType) {
RCTDisplayTypeNone,
RCTDisplayTypeFlex,
RCTDisplayTypeInline,
};
struct RCTLayoutMetrics {
CGRect frame;
CGRect contentFrame;
UIEdgeInsets borderWidth;
RCTDisplayType displayType;
UIUserInterfaceLayoutDirection layoutDirection;
};
typedef struct CG_BOXABLE RCTLayoutMetrics RCTLayoutMetrics;
struct RCTLayoutContext {
CGPoint absolutePosition;
__unsafe_unretained NSHashTable<RCTShadowView *> *_Nonnull affectedShadowViews;
__unsafe_unretained NSHashTable<NSString *> *_Nonnull other;
};
typedef struct CG_BOXABLE RCTLayoutContext RCTLayoutContext;
static inline BOOL RCTLayoutMetricsEqualToLayoutMetrics(RCTLayoutMetrics a, RCTLayoutMetrics b)
{
return
CGRectEqualToRect(a.frame, b.frame) &&
CGRectEqualToRect(a.contentFrame, b.contentFrame) &&
UIEdgeInsetsEqualToEdgeInsets(a.borderWidth, b.borderWidth) &&
a.displayType == b.displayType &&
a.layoutDirection == b.layoutDirection;
}
RCT_EXTERN RCTLayoutMetrics RCTLayoutMetricsFromYogaNode(YGNodeRef yogaNode);
/**
* Converts float values between Yoga and CoreGraphics representations,
* especially in terms of edge cases.
*/
RCT_EXTERN float RCTYogaFloatFromCoreGraphicsFloat(CGFloat value);
RCT_EXTERN CGFloat RCTCoreGraphicsFloatFromYogaFloat(float value);
/**
* Converts compound `YGValue` to simple `CGFloat` value.
*/
RCT_EXTERN CGFloat RCTCoreGraphicsFloatFromYogaValue(YGValue value, CGFloat baseFloatValue);
/**
* Converts `YGDirection` to `UIUserInterfaceLayoutDirection` and vise versa.
*/
RCT_EXTERN YGDirection RCTYogaLayoutDirectionFromUIKitLayoutDirection(UIUserInterfaceLayoutDirection direction);
RCT_EXTERN UIUserInterfaceLayoutDirection RCTUIKitLayoutDirectionFromYogaLayoutDirection(YGDirection direction);
/**
* Converts `YGDisplay` to `RCTDisplayType` and vise versa.
*/
RCT_EXTERN YGDisplay RCTYogaDisplayTypeFromReactDisplayType(RCTDisplayType displayType);
RCT_EXTERN RCTDisplayType RCTReactDisplayTypeFromYogaDisplayType(YGDisplay displayType);
NS_ASSUME_NONNULL_END

145
React/Views/RCTLayout.m Normal file
Просмотреть файл

@ -0,0 +1,145 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <yoga/Yoga.h>
#import "RCTAssert.h"
#import "RCTShadowView+Layout.h"
RCTLayoutMetrics RCTLayoutMetricsFromYogaNode(YGNodeRef yogaNode)
{
RCTLayoutMetrics layoutMetrics;
CGRect frame = (CGRect){
(CGPoint){
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetLeft(yogaNode)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetTop(yogaNode))
},
(CGSize){
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetWidth(yogaNode)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetHeight(yogaNode))
}
};
UIEdgeInsets padding = (UIEdgeInsets){
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetPadding(yogaNode, YGEdgeTop)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetPadding(yogaNode, YGEdgeLeft)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetPadding(yogaNode, YGEdgeBottom)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetPadding(yogaNode, YGEdgeRight))
};
UIEdgeInsets borderWidth = (UIEdgeInsets){
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetBorder(yogaNode, YGEdgeTop)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetBorder(yogaNode, YGEdgeLeft)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetBorder(yogaNode, YGEdgeBottom)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetBorder(yogaNode, YGEdgeRight))
};
UIEdgeInsets compoundInsets = (UIEdgeInsets){
borderWidth.top + padding.top,
borderWidth.left + padding.left,
borderWidth.bottom + padding.bottom,
borderWidth.right + padding.right
};
CGRect bounds = (CGRect){CGPointZero, frame.size};
CGRect contentFrame = UIEdgeInsetsInsetRect(bounds, compoundInsets);
layoutMetrics.frame = frame;
layoutMetrics.borderWidth = borderWidth;
layoutMetrics.contentFrame = contentFrame;
layoutMetrics.displayType = RCTReactDisplayTypeFromYogaDisplayType(YGNodeStyleGetDisplay(yogaNode));
layoutMetrics.layoutDirection = RCTUIKitLayoutDirectionFromYogaLayoutDirection(YGNodeLayoutGetDirection(yogaNode));
return layoutMetrics;
}
/**
* Yoga and CoreGraphics have different opinions about how "infinity" value
* should be represented.
* Yoga uses `NAN` which requires additional effort to compare all those values,
* whereas GoreGraphics uses `GFLOAT_MAX` which can be easyly compared with
* standard `==` operator.
*/
float RCTYogaFloatFromCoreGraphicsFloat(CGFloat value)
{
if (value == CGFLOAT_MAX || isnan(value) || isinf(value)) {
return YGUndefined;
}
return value;
}
CGFloat RCTCoreGraphicsFloatFromYogaFloat(float value)
{
if (value == YGUndefined || isnan(value) || isinf(value)) {
return CGFLOAT_MAX;
}
return value;
}
CGFloat RCTCoreGraphicsFloatFromYogaValue(YGValue value, CGFloat baseFloatValue)
{
switch (value.unit) {
case YGUnitPoint:
return RCTCoreGraphicsFloatFromYogaFloat(value.value);
case YGUnitPercent:
return RCTCoreGraphicsFloatFromYogaFloat(value.value) * baseFloatValue;
case YGUnitAuto:
case YGUnitUndefined:
return baseFloatValue;
}
}
YGDirection RCTYogaLayoutDirectionFromUIKitLayoutDirection(UIUserInterfaceLayoutDirection direction)
{
switch (direction) {
case UIUserInterfaceLayoutDirectionRightToLeft:
return YGDirectionRTL;
case UIUserInterfaceLayoutDirectionLeftToRight:
return YGDirectionLTR;
}
}
UIUserInterfaceLayoutDirection RCTUIKitLayoutDirectionFromYogaLayoutDirection(YGDirection direction)
{
switch (direction) {
case YGDirectionInherit:
case YGDirectionLTR:
return UIUserInterfaceLayoutDirectionLeftToRight;
case YGDirectionRTL:
return UIUserInterfaceLayoutDirectionRightToLeft;
}
}
YGDisplay RCTYogaDisplayTypeFromReactDisplayType(RCTDisplayType displayType)
{
switch (displayType) {
case RCTDisplayTypeNone:
return YGDisplayNone;
case RCTDisplayTypeFlex:
return YGDisplayFlex;
case RCTDisplayTypeInline:
RCTAssert(NO, @"RCTDisplayTypeInline cannot be converted to YGDisplay value.");
return YGDisplayNone;
}
}
RCTDisplayType RCTReactDisplayTypeFromYogaDisplayType(YGDisplay displayType)
{
switch (displayType) {
case YGDisplayFlex:
return RCTDisplayTypeFlex;
case YGDisplayNone:
return RCTDisplayTypeNone;
}
}

Просмотреть файл

@ -25,10 +25,6 @@
*/
@property (nonatomic, assign) YGDirection baseDirection;
/**
* Calculate all views whose frame needs updating after layout has been calculated.
* Returns a set contains the shadowviews that need updating.
*/
- (NSSet<RCTShadowView *> *)collectViewsWithUpdatedFrames;
- (void)layoutWithAffectedShadowViews:(NSHashTable<RCTShadowView *> *)affectedShadowViews;
@end

Просмотреть файл

@ -10,30 +10,33 @@
#import "RCTRootShadowView.h"
#import "RCTI18nUtil.h"
#import "RCTShadowView+Layout.h"
@implementation RCTRootShadowView
- (instancetype)init
{
self = [super init];
if (self) {
if (self = [super init]) {
_baseDirection = [[RCTI18nUtil sharedInstance] isRTL] ? YGDirectionRTL : YGDirectionLTR;
_availableSize = CGSizeMake(INFINITY, INFINITY);
}
return self;
}
- (NSSet<RCTShadowView *> *)collectViewsWithUpdatedFrames
- (void)layoutWithAffectedShadowViews:(NSHashTable<RCTShadowView *> *)affectedShadowViews
{
// Treating `INFINITY` as `YGUndefined` (which equals `NAN`).
float availableWidth = _availableSize.width == INFINITY ? YGUndefined : _availableSize.width;
float availableHeight = _availableSize.height == INFINITY ? YGUndefined : _availableSize.height;
NSHashTable<NSString *> *other = [NSHashTable new];
YGNodeCalculateLayout(self.yogaNode, availableWidth, availableHeight, _baseDirection);
RCTLayoutContext layoutContext = {};
layoutContext.absolutePosition = CGPointZero;
layoutContext.affectedShadowViews = affectedShadowViews;
layoutContext.other = other;
NSMutableSet<RCTShadowView *> *viewsWithNewFrame = [NSMutableSet set];
[self applyLayoutNode:self.yogaNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];
return viewsWithNewFrame;
[self layoutWithMinimumSize:CGSizeZero
maximumSize:_availableSize
layoutDirection:RCTUIKitLayoutDirectionFromYogaLayoutDirection(_baseDirection)
layoutContext:layoutContext];
}
@end

Просмотреть файл

@ -11,13 +11,6 @@
#import <React/RCTShadowView.h>
/**
* Converts float values between Yoga and CoreGraphics representations,
* especially in terms of edge cases.
*/
RCT_EXTERN float RCTYogaFloatFromCoreGraphicsFloat(CGFloat value);
RCT_EXTERN CGFloat RCTCoreGraphicsFloatFromYogaFloat(float value);
@interface RCTShadowView (Layout)
#pragma mark - Computed Layout-Inferred Metrics
@ -28,13 +21,6 @@ RCT_EXTERN CGFloat RCTCoreGraphicsFloatFromYogaFloat(float value);
@property (nonatomic, readonly) CGSize availableSize;
@property (nonatomic, readonly) CGRect contentFrame;
#pragma mark - Measuring
/**
* Measures shadow view without side-effects.
*/
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize;
#pragma mark - Dirty Propagation Control
/**

Просмотреть файл

@ -11,31 +11,7 @@
#import <yoga/Yoga.h>
/**
* Yoga and CoreGraphics have different opinions about how "infinity" value
* should be represented.
* Yoga uses `NAN` which requires additional effort to compare all those values,
* whereas GoreGraphics uses `GFLOAT_MAX` which can be easyly compared with
* standard `==` operator.
*/
float RCTYogaFloatFromCoreGraphicsFloat(CGFloat value)
{
if (value == CGFLOAT_MAX || isnan(value) || isinf(value)) {
return YGUndefined;
}
return value;
}
CGFloat RCTCoreGraphicsFloatFromYogaFloat(float value)
{
if (value == YGUndefined || isnan(value) || isinf(value)) {
return CGFLOAT_MAX;
}
return value;
}
#import "RCTAssert.h"
@implementation RCTShadowView (Layout)
@ -78,46 +54,12 @@ CGFloat RCTCoreGraphicsFloatFromYogaFloat(float value)
- (CGSize)availableSize
{
return UIEdgeInsetsInsetRect((CGRect){CGPointZero, self.frame.size}, self.compoundInsets).size;
return self.layoutMetrics.contentFrame.size;
}
- (CGRect)contentFrame
{
CGRect bounds = (CGRect){CGPointZero, self.frame.size};
return UIEdgeInsetsInsetRect(bounds, self.compoundInsets);
}
#pragma mark - Measuring
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
{
YGNodeRef clonnedYogaNode = YGNodeClone(self.yogaNode);
YGNodeRef constraintYogaNode = YGNodeNewWithConfig([[self class] yogaConfig]);
YGNodeInsertChild(constraintYogaNode, clonnedYogaNode, 0);
YGNodeStyleSetMinWidth(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.width));
YGNodeStyleSetMinHeight(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.height));
YGNodeStyleSetMaxWidth(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(maximumSize.width));
YGNodeStyleSetMaxHeight(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(maximumSize.height));
YGNodeCalculateLayout(
constraintYogaNode,
YGUndefined,
YGUndefined,
self.layoutDirection
);
CGSize measuredSize = (CGSize){
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetWidth(constraintYogaNode)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetHeight(constraintYogaNode)),
};
YGNodeRemoveChild(constraintYogaNode, clonnedYogaNode);
YGNodeFree(constraintYogaNode);
YGNodeFree(clonnedYogaNode);
return measuredSize;
return self.layoutMetrics.contentFrame;
}
#pragma mark - Dirty Propagation Control

Просмотреть файл

@ -10,6 +10,7 @@
#import <UIKit/UIKit.h>
#import <React/RCTComponent.h>
#import <React/RCTLayout.h>
#import <React/RCTRootView.h>
#import <yoga/Yoga.h>
@ -51,6 +52,11 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
@property (nonatomic, copy) NSString *viewName;
@property (nonatomic, copy) RCTDirectEventBlock onLayout;
/**
* Computed layout of the view.
*/
@property (nonatomic, assign) RCTLayoutMetrics layoutMetrics;
/**
* In some cases we need a way to specify some environmental data to shadow view
* to improve layout (or do something similar), so `localData` serves these needs.
@ -70,12 +76,6 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
*/
@property (nonatomic, assign, getter=isNewView) BOOL newView;
/**
* Computed layout direction of the view.
*/
@property (nonatomic, assign, readonly) UIUserInterfaceLayoutDirection layoutDirection;
/**
* Position and dimensions.
* Defaults to { 0, 0, NAN, NAN }.
@ -167,38 +167,39 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
*/
@property (nonatomic, assign) YGOverflow overflow;
/**
* Computed position of the view.
*/
@property (nonatomic, assign, readonly) CGRect frame;
/**
* Represents the natural size of the view, which is used when explicit size is not set or is ambiguous.
* Defaults to `{UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric}`.
*/
@property (nonatomic, assign) CGSize intrinsicContentSize;
/**
* Apply the CSS layout.
* This method also calls `applyLayoutToChildren:` internally. The functionality
* is split into two methods so subclasses can override `applyLayoutToChildren:`
* while using default implementation of `applyLayoutNode:`.
*/
- (void)applyLayoutNode:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER;
- (void)applyLayoutWithFrame:(CGRect)frame
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
viewsWithUpdatedLayout:(NSMutableSet<RCTShadowView *> *)viewsWithUpdatedLayout
absolutePosition:(CGPoint)absolutePosition;
#pragma mark - Layout
/**
* Enumerate the child nodes and tell them to apply layout.
* Initiates layout starts from the view.
*/
- (void)applyLayoutToChildren:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition;
- (void)layoutWithMinimumSize:(CGSize)minimumSize
maximumSize:(CGSize)maximumSize
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
layoutContext:(RCTLayoutContext)layoutContext;
/**
* Applies computed layout metrics to the view.
*/
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
layoutContext:(RCTLayoutContext)layoutContext;
/**
* Calculates (if needed) and applies layout to subviews.
*/
- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext;
/**
* Measures shadow view without side-effects.
* Default implementation uses Yoga for measuring.
*/
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize
maximumSize:(CGSize)maximumSize;
/**
* Returns whether or not this view can have any subviews.

Просмотреть файл

@ -12,6 +12,7 @@
#import "RCTConvert.h"
#import "RCTI18nUtil.h"
#import "RCTLog.h"
#import "RCTShadowView+Layout.h"
#import "RCTUtils.h"
#import "UIView+Private.h"
#import "UIView+React.h"
@ -153,82 +154,21 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
YGNodeStyleSetBorder(node, YGEdgeAll, metaProps[META_PROP_ALL].value);
}
- (void)applyLayoutNode:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
if (!YGNodeGetHasNewLayout(node)) {
return;
}
RCTAssert(!YGNodeIsDirty(node), @"Attempt to get layout metrics from dirtied Yoga node.");
YGNodeSetHasNewLayout(node, false);
if (YGNodeStyleGetDisplay(node) == YGDisplayNone) {
// If the node is hidden (has `display: none;`), its (and its descendants)
// layout metrics are invalid and/or dirtied, so we have to stop here.
return;
}
CGRect frame = CGRectMake(YGNodeLayoutGetLeft(node), YGNodeLayoutGetTop(node), YGNodeLayoutGetWidth(node), YGNodeLayoutGetHeight(node));
// Even if `YGNodeLayoutGetDirection` can return `YGDirectionInherit` here, it actually means
// that Yoga will use LTR layout for the view (even if layout process is not finished yet).
UIUserInterfaceLayoutDirection layoutDirection = YGNodeLayoutGetDirection(_yogaNode) == YGDirectionRTL ? UIUserInterfaceLayoutDirectionRightToLeft : UIUserInterfaceLayoutDirectionLeftToRight;
[self applyLayoutWithFrame:frame
layoutDirection:layoutDirection
viewsWithUpdatedLayout:viewsWithNewFrame
absolutePosition:absolutePosition];
}
- (void)applyLayoutWithFrame:(CGRect)frame
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
viewsWithUpdatedLayout:(NSMutableSet<RCTShadowView *> *)viewsWithUpdatedLayout
absolutePosition:(CGPoint)absolutePosition
{
if (!CGRectEqualToRect(_frame, frame) || _layoutDirection != layoutDirection) {
_frame = frame;
_layoutDirection = layoutDirection;
[viewsWithUpdatedLayout addObject:self];
}
absolutePosition.x += frame.origin.x;
absolutePosition.y += frame.origin.y;
[self applyLayoutToChildren:_yogaNode
viewsWithNewFrame:viewsWithUpdatedLayout
absolutePosition:absolutePosition];
}
- (void)applyLayoutToChildren:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
{
for (unsigned int i = 0; i < YGNodeGetChildCount(node); ++i) {
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
[child applyLayoutNode:YGNodeGetChild(node, i)
viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition];
}
}
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
{
CGPoint offset = CGPointZero;
NSInteger depth = 30; // max depth to search
RCTShadowView *shadowView = self;
while (depth && shadowView && shadowView != ancestor) {
offset.x += shadowView.frame.origin.x;
offset.y += shadowView.frame.origin.y;
offset.x += shadowView.layoutMetrics.frame.origin.x;
offset.y += shadowView.layoutMetrics.frame.origin.y;
shadowView = shadowView->_superview;
depth--;
}
if (ancestor != shadowView) {
return CGRectNull;
}
return (CGRect){offset, self.frame.size};
return (CGRect){offset, self.layoutMetrics.frame.size};
}
- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor
@ -244,9 +184,7 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
- (instancetype)init
{
if ((self = [super init])) {
_frame = CGRectMake(0, 0, YGUndefined, YGUndefined);
if (self = [super init]) {
for (unsigned int ii = 0; ii < META_PROP_COUNT; ii++) {
_paddingMetaProps[ii] = YGValueUndefined;
_marginMetaProps[ii] = YGValueUndefined;
@ -316,12 +254,125 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
return _superview;
}
#pragma mark - Layout
- (void)layoutWithMinimumSize:(CGSize)minimumSize
maximumSize:(CGSize)maximumSize
layoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
layoutContext:(RCTLayoutContext)layoutContext
{
YGNodeRef yogaNode = _yogaNode;
CGSize oldMinimumSize = (CGSize){
RCTCoreGraphicsFloatFromYogaValue(YGNodeStyleGetMinWidth(yogaNode), 0.0),
RCTCoreGraphicsFloatFromYogaValue(YGNodeStyleGetMinHeight(yogaNode), 0.0)
};
if (!CGSizeEqualToSize(oldMinimumSize, minimumSize)) {
YGNodeStyleSetMinWidth(yogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.width));
YGNodeStyleSetMinHeight(yogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.height));
}
YGNodeCalculateLayout(
yogaNode,
RCTYogaFloatFromCoreGraphicsFloat(maximumSize.width),
RCTYogaFloatFromCoreGraphicsFloat(maximumSize.height),
RCTYogaLayoutDirectionFromUIKitLayoutDirection(layoutDirection)
);
RCTAssert(!YGNodeIsDirty(yogaNode), @"Attempt to get layout metrics from dirtied Yoga node.");
if (!YGNodeGetHasNewLayout(yogaNode)) {
return;
}
RCTLayoutMetrics layoutMetrics = RCTLayoutMetricsFromYogaNode(yogaNode);
layoutContext.absolutePosition.x += layoutMetrics.frame.origin.x;
layoutContext.absolutePosition.y += layoutMetrics.frame.origin.y;
[self layoutWithMetrics:layoutMetrics
layoutContext:layoutContext];
[self layoutSubviewsWithContext:layoutContext];
}
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
layoutContext:(RCTLayoutContext)layoutContext
{
if (!RCTLayoutMetricsEqualToLayoutMetrics(self.layoutMetrics, layoutMetrics)) {
self.layoutMetrics = layoutMetrics;
[layoutContext.affectedShadowViews addObject:self];
}
}
- (void)layoutSubviewsWithContext:(RCTLayoutContext)layoutContext
{
RCTLayoutMetrics layoutMetrics = self.layoutMetrics;
if (layoutMetrics.displayType == RCTDisplayTypeNone) {
return;
}
for (RCTShadowView *childShadowView in _reactSubviews) {
YGNodeRef childYogaNode = childShadowView.yogaNode;
RCTAssert(!YGNodeIsDirty(childYogaNode), @"Attempt to get layout metrics from dirtied Yoga node.");
if (!YGNodeGetHasNewLayout(childYogaNode)) {
continue;
}
RCTLayoutMetrics childLayoutMetrics = RCTLayoutMetricsFromYogaNode(childYogaNode);
layoutContext.absolutePosition.x += childLayoutMetrics.frame.origin.x;
layoutContext.absolutePosition.y += childLayoutMetrics.frame.origin.y;
[childShadowView layoutWithMetrics:childLayoutMetrics
layoutContext:layoutContext];
// Recursive call.
[childShadowView layoutSubviewsWithContext:layoutContext];
}
}
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
{
YGNodeRef clonnedYogaNode = YGNodeClone(self.yogaNode);
YGNodeRef constraintYogaNode = YGNodeNewWithConfig([[self class] yogaConfig]);
YGNodeInsertChild(constraintYogaNode, clonnedYogaNode, 0);
YGNodeStyleSetMinWidth(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.width));
YGNodeStyleSetMinHeight(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(minimumSize.height));
YGNodeStyleSetMaxWidth(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(maximumSize.width));
YGNodeStyleSetMaxHeight(constraintYogaNode, RCTYogaFloatFromCoreGraphicsFloat(maximumSize.height));
YGNodeCalculateLayout(
constraintYogaNode,
YGUndefined,
YGUndefined,
self.layoutMetrics.layoutDirection
);
CGSize measuredSize = (CGSize){
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetWidth(constraintYogaNode)),
RCTCoreGraphicsFloatFromYogaFloat(YGNodeLayoutGetHeight(constraintYogaNode)),
};
YGNodeRemoveChild(constraintYogaNode, clonnedYogaNode);
YGNodeFree(constraintYogaNode);
YGNodeFree(clonnedYogaNode);
return measuredSize;
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
for (RCTShadowView *shadowView in _reactSubviews) {
if (CGRectContainsPoint(shadowView.frame, point)) {
if (CGRectContainsPoint(shadowView.layoutMetrics.frame, point)) {
CGPoint relativePoint = point;
CGPoint origin = shadowView.frame.origin;
CGPoint origin = shadowView.layoutMetrics.frame.origin;
relativePoint.x -= origin.x;
relativePoint.y -= origin.y;
return [shadowView reactTagAtPoint:relativePoint];
@ -333,7 +384,7 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT],
- (NSString *)description
{
NSString *description = super.description;
description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)];
description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.layoutMetrics.frame)];
return description;
}

Просмотреть файл

@ -13,43 +13,22 @@
#import "RCTUtils.h"
@interface RCTShadowView () {
// This will be removed after t15757916, which will remove
// side-effects from `setFrame:` method.
@public CGRect _frame;
}
@end
@implementation RCTScrollContentShadowView
- (void)applyLayoutNode:(YGNodeRef)node
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
absolutePosition:(CGPoint)absolutePosition
- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
layoutContext:(RCTLayoutContext)layoutContext
{
// Call super method if LTR layout is enforced.
if (YGNodeLayoutGetDirection(self.yogaNode) != YGDirectionRTL) {
[super applyLayoutNode:node
viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition];
return;
if (layoutMetrics.layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
// Motivation:
// Yoga place `contentView` on the right side of `scrollView` when RTL layout is enfoced.
// That breaks everything; it is completely pointless to (re)position `contentView`
// because it is `contentView`'s job. So, we work around it here.
layoutContext.absolutePosition.x += layoutMetrics.frame.size.width;
layoutMetrics.frame.origin.x = 0;
}
// Motivation:
// Yoga place `contentView` on the right side of `scrollView` when RTL layout is enfoced.
// That breaks everything; it is completely pointless to (re)position `contentView`
// because it is `contentView`'s job. So, we work around it here.
// Step 1. Compensate `absolutePosition` change.
CGFloat xCompensation = YGNodeLayoutGetRight(node) - YGNodeLayoutGetLeft(node);
absolutePosition.x += xCompensation;
// Step 2. Call super method.
[super applyLayoutNode:node
viewsWithNewFrame:viewsWithNewFrame
absolutePosition:absolutePosition];
// Step 3. Reset the position.
_frame.origin.x = RCTRoundPixelValue(YGNodeLayoutGetRight(node));
[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext];
}
@end