From e52cab5a7f44aee5c145e9995850ab8051829b5c Mon Sep 17 00:00:00 2001 From: alvaromb Date: Wed, 3 Aug 2016 04:01:45 -0700 Subject: [PATCH] Adds the ability to use UIManager to check if a node is an ancestor Summary: Sometimes is handy to check if a React node is a descendant of another node or not. For instance, I want to check if the focused `TextInput` is descendant of an specific `ScrollView`: ```js const currentlyFocusedField = TextInput.State.currentlyFocusedField() UIManager.viewIsAncestorOf( currentlyFocusedField, this.getInnerViewNode(), (isAncestor) => { if (isAncestor) { console.log('The focused field is a descendant of this ScrollView!') } } ) ``` This function uses the same strategy as the `measureLayout` method to check if one node is an ancestor of other node. As the `measureLayout` method, this is performed outside the main thread. By now I've only implemented the iOS version and its tests, but if this function is going to be merged I'll implement the Android version too. I have objc experience but no Java or Android, so I prefer to validate this functionality before jumping into developing the Android part. Closes https://github.com/facebook/react-native/pull/7876 Differential Revision: D3662045 Pulled By: javache fbshipit-source-id: b9668e8ea94fd01db76651f16243926cf9c2566f --- .../UIExplorerUnitTests/RCTShadowViewTests.m | 23 +++++++++++++++++++ React/Modules/RCTUIManager.m | 20 ++++++++++++++++ React/Views/RCTShadowView.h | 5 ++++ React/Views/RCTShadowView.m | 11 +++++++++ 4 files changed, 59 insertions(+) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index 0d586cc16..3f003dbe9 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -106,6 +106,29 @@ 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 b55135742..538428c02 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1226,6 +1226,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 58c9dbbf7..c24cc4049 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 f3da342d4..c67484d18 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -279,6 +279,17 @@ DEFINE_PROCESS_META_PROPS(Border); 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])) {