Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions React/Modules/RCTUIManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ @implementation RCTUIManager

// Animation
RCTLayoutAnimation *_layoutAnimation; // Main thread only
NSMutableArray<id<RCTComponent>> *_deleteAnimatedViews; // Main thread only

NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
Expand Down Expand Up @@ -318,6 +319,8 @@ - (void)setBridge:(RCTBridge *)bridge

_bridgeTransactionListeners = [NSMutableSet new];

_deleteAnimatedViews = [NSMutableArray new];

// Get view managers from bridge
NSMutableDictionary *componentDataByName = [NSMutableDictionary new];
for (Class moduleClass in _bridge.moduleClasses) {
Expand Down Expand Up @@ -778,10 +781,20 @@ - (void)_amendPendingUIBlocksWithStylePropagationUpdateForShadowView:(RCTShadowV

- (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
fromContainer:(id<RCTComponent>)container
permanent:(BOOL)permanent
{
RCTLayoutAnimation *layoutAnimation = _layoutAnimation;
RCTAnimation *deleteAnimation = layoutAnimation.deleteAnimation;
for (id<RCTComponent> removedChild in children) {
[container removeReactSubview:removedChild];
}
}

/**
* Remove children views from their parent with an animation.
*/
- (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
fromContainer:(id<RCTComponent>)container
withAnimation:(RCTLayoutAnimation *)animation
{
RCTAnimation *deleteAnimation = animation.deleteAnimation;

__block NSUInteger completionsCalled = 0;

Expand All @@ -790,20 +803,23 @@ - (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
void (^completion)(BOOL) = ^(BOOL finished) {
completionsCalled++;

[_deleteAnimatedViews removeObject: removedChild];
[container removeReactSubview:removedChild];

if (layoutAnimation.callback && completionsCalled == children.count) {
layoutAnimation.callback(@[@(finished)]);
if (animation.callback && completionsCalled == children.count) {
animation.callback(@[@(finished)]);

// It's unsafe to call this callback more than once, so we nil it out here
// to make sure that doesn't happen.
layoutAnimation.callback = nil;
animation.callback = nil;
}
};

if (permanent && deleteAnimation && [removedChild isKindOfClass: [UIView class]]) {
if ([removedChild isKindOfClass: [UIView class]]) {
UIView *view = (UIView *)removedChild;

[_deleteAnimatedViews addObject: view];

// Disable user interaction while the view is animating since JS won't receive
// the view events anyway.
view.userInteractionEnabled = NO;
Expand All @@ -825,6 +841,7 @@ - (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
}
}


RCT_EXPORT_METHOD(removeRootView:(nonnull NSNumber *)rootReactTag)
{
RCTShadowView *rootShadowView = _shadowViewRegistry[rootReactTag];
Expand Down Expand Up @@ -938,8 +955,16 @@ - (void)_manageChildren:(NSNumber *)containerReactTag
[self _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
NSArray<id<RCTComponent>> *temporarilyRemovedChildren =
[self _childrenToRemoveFromContainer:container atIndices:moveFromIndices];
[self _removeChildren:permanentlyRemovedChildren fromContainer:container permanent:true];
[self _removeChildren:temporarilyRemovedChildren fromContainer:container permanent:false];

if (_layoutAnimation.deleteAnimation) {
[self _removeChildren:permanentlyRemovedChildren
fromContainer:container
withAnimation:_layoutAnimation];
} else {
[self _removeChildren:permanentlyRemovedChildren fromContainer:container];
}

[self _removeChildren:temporarilyRemovedChildren fromContainer:container];

[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];

Expand All @@ -960,8 +985,22 @@ - (void)_manageChildren:(NSNumber *)containerReactTag
NSArray<NSNumber *> *sortedIndices =
[destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)];
for (NSNumber *reactIndex in sortedIndices) {
NSInteger insertAtIndex = reactIndex.integerValue;

// When performing layout delete animations views are not removed immediatly
// from their container so we need to offset the insert index if a view
// that is going to be removed is before the view we want to insert.
if ([_deleteAnimatedViews count] > 0) {
for (NSInteger index = 0; index < insertAtIndex; index++) {
id<RCTComponent> subview = [container reactSubviews][index];
if ([_deleteAnimatedViews containsObject:subview]) {
insertAtIndex++;
}
}
}

[container insertReactSubview:destinationsToChildrenToAdd[reactIndex]
atIndex:reactIndex.integerValue];
atIndex:insertAtIndex];
}
}

Expand Down