diff --git a/RNTester/RNTesterUnitTests/RCTShadowViewTests.m b/RNTester/RNTesterUnitTests/RCTShadowViewTests.m index d5cb09c89c1..eecd901f582 100644 --- a/RNTester/RNTesterUnitTests/RCTShadowViewTests.m +++ b/RNTester/RNTesterUnitTests/RCTShadowViewTests.m @@ -100,6 +100,29 @@ XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200))); } +- (void)testAncestorCheck +{ + RCTShadowView *centerView = [self _shadowViewWithConfig:^(YGNodeRef node) { + YGNodeStyleSetFlex(node, 1); + }]; + + RCTShadowView *mainView = [self _shadowViewWithConfig:^(YGNodeRef node) { + YGNodeStyleSetFlex(node, 1); + }]; + + [mainView insertReactSubview:centerView atIndex:0]; + + RCTShadowView *footerView = [self _shadowViewWithConfig:^(YGNodeRef node) { + YGNodeStyleSetFlex(node, 1); + }]; + + [self.parentView insertReactSubview:mainView atIndex:0]; + [self.parentView insertReactSubview:footerView atIndex:1]; + + XCTAssertTrue([centerView viewIsDescendantOf:mainView]); + XCTAssertFalse([footerView viewIsDescendantOf:mainView]); +} + - (void)testAssignsSuggestedWidthDimension { [self _withShadowViewWithStyle:^(YGNodeRef node) { diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 54c6b5cbd7d..1d7ac52a941 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1321,6 +1321,26 @@ RCT_EXPORT_METHOD(measureInWindow:(nonnull NSNumber *)reactTag }]; } +/** + * Returs if the shadow view provided has the `ancestor` shadow view as + * an actual ancestor. + */ +RCT_EXPORT_METHOD(viewIsDescendantOf:(nonnull NSNumber *)reactTag + ancestor:(nonnull NSNumber *)ancestorReactTag + callback:(RCTResponseSenderBlock)callback) +{ + RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; + RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag]; + if (!shadowView) { + return; + } + if (!ancestorShadowView) { + return; + } + BOOL viewIsAncestor = [shadowView viewIsDescendantOf:ancestorShadowView]; + callback(@[@(viewIsAncestor)]); +} + static void RCTMeasureLayout(RCTShadowView *view, RCTShadowView *ancestor, RCTResponseSenderBlock callback) diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 326b8bc90e9..8d9828cb98a 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -235,4 +235,9 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry */ - (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor; +/** + * Checks if the current shadow view is a descendant of the provided `ancestor` + */ +- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor; + @end diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index ecac8071eef..40c0cdaf8d8 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -170,6 +170,17 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], return (CGRect){offset, self.layoutMetrics.frame.size}; } +- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor +{ + NSInteger depth = 30; // max depth to search + RCTShadowView *shadowView = self; + while (depth && shadowView && shadowView != ancestor) { + shadowView = shadowView->_superview; + depth--; + } + return ancestor == shadowView; +} + - (instancetype)init { if (self = [super init]) {