Skip to content

Commit

Permalink
Adds the ability to use UIManager to check if a node is an ancestor
Browse files Browse the repository at this point in the history
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 #7876

Differential Revision: D3662045

Pulled By: javache

fbshipit-source-id: b9668e8ea94fd01db76651f16243926cf9c2566f
  • Loading branch information
alvaromb authored and Facebook Github Bot 9 committed Aug 3, 2016
1 parent 59a1311 commit e52cab5
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 0 deletions.
23 changes: 23 additions & 0 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions React/Modules/RCTUIManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions React/Views/RCTShadowView.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,9 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
*/
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor;

/**
* Checks if the current shadow view is a descendant of the provided `ancestor`
*/
- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor;

@end
11 changes: 11 additions & 0 deletions React/Views/RCTShadowView.m
Original file line number Diff line number Diff line change
Expand Up @@ -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])) {
Expand Down

0 comments on commit e52cab5

Please sign in to comment.