mirror of
https://github.com/status-im/react-native.git
synced 2025-02-03 21:24:31 +00:00
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:
parent
47b36d3ff0
commit
f91f7d91a1
@ -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
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
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user