Skip to content

Commit

Permalink
Refactored subview management
Browse files Browse the repository at this point in the history
Summary:
This diff refactors the view update process into two stages:

1. The `reactSubviews` array is set, whose order matches the order of the JS components and shadowView components, as specified by the UIManager.
2. The `didUpdateReactSubviews` method is called, which actually inserts the reactSubviews into the view hierarchy.

This simplifies a lot of the hacks we had for special-case treatment of subviews: In many cases we don't want to actually insert `reactSubviews` into the parentView, and we had a bunch of component-specific solutions for that (typically overriding all of the reactSubviews methods to store views in an array). Now, we can simply override the `didUpdateReactSubviews` method for those views to do nothing, or do something different.

Reviewed By: wwjholmes

Differential Revision: D3396594

fbshipit-source-id: 92fc56fd31db0cfc66aac3d1634a4d4ae3903085
  • Loading branch information
nicklockwood authored and Facebook Github Bot 7 committed Jun 7, 2016
1 parent 2a92b52 commit 46c02b6
Show file tree
Hide file tree
Showing 21 changed files with 170 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ - (void)layoutSubviews
- (NSArray<UIView<RCTComponent> *> *)reactSubviews
{
// this is to avoid unregistering our RCTRootView when the component is removed from RN hierarchy
(void)[super reactSubviews];
return @[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ - (void)changeColor
- (NSArray<UIView<RCTComponent> *> *)reactSubviews
{
// this is to avoid unregistering our RCTRootView when the component is removed from RN hierarchy
(void)[super reactSubviews];
return @[];
}

Expand Down
10 changes: 5 additions & 5 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ - (void)testManagingChildrenToAddViews
@"Expect to have 5 react subviews after calling manage children \
with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]);
for (UIView *view in addedViews) {
XCTAssertTrue([view superview] == containerView,
@"Expected to have manage children successfully add children");
XCTAssertTrue([view reactSuperview] == containerView,
@"Expected to have manage children successfully add children");
[view removeFromSuperview];
}
}
Expand All @@ -95,7 +95,7 @@ - (void)testManagingChildrenToRemoveViews
}
for (NSInteger i = 2; i < 20; i++) {
UIView *view = _uiManager.viewRegistry[@(i)];
[containerView addSubview:view];
[containerView insertReactSubview:view atIndex:containerView.reactSubviews.count];
}

// Remove views 1-5 from view 20
Expand All @@ -112,7 +112,7 @@ - (void)testManagingChildrenToRemoveViews
with 5 tags to remove and 18 prior children, instead have %zd",
containerView.reactSubviews.count);
for (UIView *view in removedViews) {
XCTAssertTrue([view superview] == nil,
XCTAssertTrue([view reactSuperview] == nil,
@"Expected to have manage children successfully remove children");
// After removing views are unregistered - we need to reregister
_uiManager.viewRegistry[view.reactTag] = view;
Expand Down Expand Up @@ -155,7 +155,7 @@ - (void)testManagingChildrenToAddRemoveAndMove

for (NSInteger i = 1; i < 11; i++) {
UIView *view = _uiManager.viewRegistry[@(i)];
[containerView addSubview:view];
[containerView insertReactSubview:view atIndex:containerView.reactSubviews.count];
}

[_uiManager _manageChildren:@20
Expand Down
17 changes: 3 additions & 14 deletions Libraries/Text/RCTText.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ static void collectNonTextDescendants(RCTText *view, NSMutableArray *nonTextDesc
@implementation RCTText
{
NSTextStorage *_textStorage;
NSMutableArray<UIView *> *_reactSubviews;
CAShapeLayer *_highlightLayer;
}

- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_textStorage = [NSTextStorage new];
_reactSubviews = [NSMutableArray array];

self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
Expand Down Expand Up @@ -68,26 +66,17 @@ - (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
self.backgroundColor = inheritedBackgroundColor;
}

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
- (void)reactUpdateSubviews
{
[_reactSubviews insertObject:subview atIndex:atIndex];
}

- (void)removeReactSubview:(UIView *)subview
{
[_reactSubviews removeObject:subview];
}

- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
// Do nothing, as subviews are managed by `setTextStorage:` method
}

- (void)setTextStorage:(NSTextStorage *)textStorage
{
if (_textStorage != textStorage) {
_textStorage = textStorage;

// Update subviews
NSMutableArray *nonTextDescendants = [NSMutableArray new];
collectNonTextDescendants(self, nonTextDescendants);
NSArray *subviews = self.subviews;
Expand Down
26 changes: 0 additions & 26 deletions Libraries/Text/RCTTextField.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
@implementation RCTTextField
{
RCTEventDispatcher *_eventDispatcher;
NSMutableArray<UIView *> *_reactSubviews;
BOOL _jsRequestingFirstResponder;
NSInteger _nativeEventCount;
BOOL _submitted;
Expand All @@ -35,7 +34,6 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
[self addTarget:self action:@selector(textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
[self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
[self addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil];
_reactSubviews = [NSMutableArray new];
_blurOnSubmit = YES;
}
return self;
Expand Down Expand Up @@ -112,30 +110,6 @@ - (void)setPlaceholder:(NSString *)placeholder
RCTUpdatePlaceholder(self);
}

- (NSArray<UIView *> *)reactSubviews
{
// TODO: do we support subviews of textfield in React?
// In any case, we should have a better approach than manually
// maintaining array in each view subclass like this
return _reactSubviews;
}

- (void)removeReactSubview:(UIView *)subview
{
// TODO: this is a bit broken - if the TextField inserts any of
// its own views below or between React's, the indices won't match
[_reactSubviews removeObject:subview];
[subview removeFromSuperview];
}

- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
// TODO: this is a bit broken - if the TextField inserts any of
// its own views below or between React's, the indices won't match
[_reactSubviews insertObject:view atIndex:atIndex];
[super insertSubview:view atIndex:atIndex];
}

- (CGRect)caretRectForPosition:(UITextPosition *)position
{
if (_caretHidden) {
Expand Down
22 changes: 7 additions & 15 deletions Libraries/Text/RCTTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ @implementation RCTTextView
NSInteger _nativeEventCount;
RCTText *_richTextView;
NSAttributedString *_pendingAttributedText;
NSMutableArray<UIView *> *_subviews;
BOOL _blockTextShouldChange;
UITextRange *_previousSelectionRange;
NSUInteger _previousTextLength;
Expand Down Expand Up @@ -98,7 +97,6 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher

_previousSelectionRange = _textView.selectedTextRange;

_subviews = [NSMutableArray new];
[self addSubview:_scrollView];
}
return self;
Expand All @@ -107,19 +105,14 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)

- (NSArray<UIView *> *)reactSubviews
{
return _subviews;
}

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
{
[super insertReactSubview:subview atIndex:index];
if ([subview isKindOfClass:[RCTText class]]) {
if (_richTextView) {
RCTLogError(@"Tried to insert a second <Text> into <TextInput> - there can only be one.");
}
_richTextView = (RCTText *)subview;
[_subviews insertObject:_richTextView atIndex:index];

// If this <TextInput> is in rich text editing mode, and the child <Text> node providing rich text
// styling has a backgroundColor, then the attributedText produced by the child <Text> node will have an
Expand All @@ -132,23 +125,22 @@ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
attrs[NSBackgroundColorAttributeName] = subview.backgroundColor;
_textView.typingAttributes = attrs;
}
} else {
[_subviews insertObject:subview atIndex:index];
[self insertSubview:subview atIndex:index];
}
}

- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
if (_richTextView == subview) {
[_subviews removeObject:_richTextView];
_richTextView = nil;
} else {
[_subviews removeObject:subview];
[subview removeFromSuperview];
}
}

- (void)reactUpdateSubviews
{
// Do nothing, as we don't allow non-text subviews
}

- (void)setMostRecentEventCount:(NSInteger)mostRecentEventCount
{
_mostRecentEventCount = mostRecentEventCount;
Expand Down
2 changes: 1 addition & 1 deletion React/Base/RCTRootView.m
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ - (instancetype)initWithFrame:(CGRect)frame
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder)

- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
RCTPerformanceLoggerEnd(RCTPLTTI);
Expand Down
14 changes: 9 additions & 5 deletions React/Modules/RCTUIManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -893,16 +893,18 @@ static void RCTSetChildren(NSNumber *containerTag,
[container insertReactSubview:view atIndex:index++];
}
}

[container didUpdateReactSubviews];
}

RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices)
{
[self _manageChildren:containerReactTag
[self _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
Expand All @@ -911,7 +913,7 @@ static void RCTSetChildren(NSNumber *containerTag,
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];

[self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
[uiManager _manageChildren:containerReactTag
[uiManager _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
Expand All @@ -921,15 +923,15 @@ static void RCTSetChildren(NSNumber *containerTag,
}];
}

- (void)_manageChildren:(NSNumber *)containerReactTag
- (void)_manageChildren:(NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
{
id<RCTComponent> container = registry[containerReactTag];
id<RCTComponent> container = registry[containerTag];
RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count);
RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add");

Expand Down Expand Up @@ -963,6 +965,8 @@ - (void)_manageChildren:(NSNumber *)containerReactTag
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex]
atIndex:reactIndex.integerValue];
}

[container didUpdateReactSubviews];
}

RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
Expand Down
5 changes: 5 additions & 0 deletions React/Views/RCTComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ typedef void (^RCTBubblingEventBlock)(NSDictionary *body);
*/
- (void)didSetProps:(NSArray<NSString *> *)changedProps;

/**
* Called each time subviews have been updated
*/
- (void)didUpdateReactSubviews;

// TODO: Deprecate this
// This method is called after layout has been performed for all views known
// to the RCTViewManager. It is only called on UIViews, not shadow views.
Expand Down
16 changes: 2 additions & 14 deletions React/Views/RCTMap.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ @implementation RCTMap
{
UIView *_legalLabel;
CLLocationManager *_locationManager;
NSMutableArray<UIView *> *_reactSubviews;
}

- (instancetype)init
{
if ((self = [super init])) {

_hasStartedRendering = NO;
_reactSubviews = [NSMutableArray new];

// Find Apple link label
for (UIView *subview in self.subviews) {
Expand All @@ -51,19 +49,9 @@ - (void)dealloc
[_regionChangeObserveTimer invalidate];
}

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
- (void)reactUpdateSubviews
{
[_reactSubviews insertObject:subview atIndex:atIndex];
}

- (void)removeReactSubview:(UIView *)subview
{
[_reactSubviews removeObject:subview];
}

- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
// Do nothing, as annotation views are managed by `setAnnotations:` method
}

- (void)layoutSubviews
Expand Down
15 changes: 8 additions & 7 deletions React/Views/RCTModalHostView.m
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,10 @@ - (void)notifyForBoundsChange:(CGRect)newBounds
}
}

- (NSArray<UIView *> *)reactSubviews
{
return _reactSubview ? @[_reactSubview] : @[];
}

- (void)insertReactSubview:(UIView *)subview atIndex:(__unused NSInteger)atIndex
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
RCTAssert(_reactSubview == nil, @"Modal view can only have one subview");
[super insertReactSubview:subview atIndex:atIndex];
[subview addGestureRecognizer:_touchHandler];
subview.autoresizingMask = UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth;
Expand All @@ -74,11 +70,16 @@ - (void)insertReactSubview:(UIView *)subview atIndex:(__unused NSInteger)atIndex
- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(subview == _reactSubview, @"Cannot remove view other than modal view");
[super removeReactSubview:subview];
[subview removeGestureRecognizer:_touchHandler];
[subview removeFromSuperview];
_reactSubview = nil;
}

- (void)didUpdateReactSubviews
{
// Do nothing, as subview (singular) is managed by `insertReactSubview:atIndex:`
}

- (void)dismissModalViewController
{
if (_isPresented) {
Expand Down
Loading

0 comments on commit 46c02b6

Please sign in to comment.