diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 10ee2152d6c256..a7fea0ef43f109 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -210,6 +210,7 @@ @implementation RCTUIManager // Animation RCTLayoutAnimation *_layoutAnimation; // Main thread only + NSMutableArray> *_deleteAnimatedViews; // Main thread only NSMutableDictionary *_shadowViewRegistry; // RCT thread only NSMutableDictionary *_viewRegistry; // Main thread only @@ -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) { @@ -778,10 +781,20 @@ - (void)_amendPendingUIBlocksWithStylePropagationUpdateForShadowView:(RCTShadowV - (void)_removeChildren:(NSArray> *)children fromContainer:(id)container - permanent:(BOOL)permanent { - RCTLayoutAnimation *layoutAnimation = _layoutAnimation; - RCTAnimation *deleteAnimation = layoutAnimation.deleteAnimation; + for (id removedChild in children) { + [container removeReactSubview:removedChild]; + } +} + +/** + * Remove children views from their parent with an animation. + */ +- (void)_removeChildren:(NSArray> *)children + fromContainer:(id)container + withAnimation:(RCTLayoutAnimation *)animation +{ + RCTAnimation *deleteAnimation = animation.deleteAnimation; __block NSUInteger completionsCalled = 0; @@ -790,20 +803,23 @@ - (void)_removeChildren:(NSArray> *)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; @@ -825,6 +841,7 @@ - (void)_removeChildren:(NSArray> *)children } } + RCT_EXPORT_METHOD(removeRootView:(nonnull NSNumber *)rootReactTag) { RCTShadowView *rootShadowView = _shadowViewRegistry[rootReactTag]; @@ -938,8 +955,16 @@ - (void)_manageChildren:(NSNumber *)containerReactTag [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; NSArray> *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]; @@ -960,8 +985,22 @@ - (void)_manageChildren:(NSNumber *)containerReactTag NSArray *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 subview = [container reactSubviews][index]; + if ([_deleteAnimatedViews containsObject:subview]) { + insertAtIndex++; + } + } + } + [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] - atIndex:reactIndex.integerValue]; + atIndex:insertAtIndex]; } }