diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index 0d586cc166f70e..3f003dbe9d608e 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -106,6 +106,29 @@ - (void)testApplyingLayoutRecursivelyToShadowView XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200))); } +- (void)testAncestorCheck +{ + RCTShadowView *centerView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); + }]; + + RCTShadowView *mainView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(node, 1); + }]; + + [mainView insertReactSubview:centerView atIndex:0]; + + RCTShadowView *footerView = [self _shadowViewWithConfig:^(CSSNodeRef node) { + CSSNodeStyleSetFlex(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:^(CSSNodeRef node) { diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index b551357427d41b..538428c0286adf 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1226,6 +1226,26 @@ - (void)setNeedsLayout }]; } +/** + * 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 58c9dbbf7d7a6d..c24cc404937b7b 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -212,4 +212,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 f3da342d430307..c67484d1802c90 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -279,6 +279,17 @@ - (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor return (CGRect){offset, self.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])) {