Fixed possible inconsistency in view hierarchy caused by deleting animation
Summary: This diff fixes a possibly inconsistent state of view hierarchy caused by async delayed deleting manipulation on UIView's tree. Even if new approach may seem tricky, the previous one was just terribly wrong. Reviewed By: javache Differential Revision: D5374670 fbshipit-source-id: 36f27330aa8b0e4e00fe43739afe3bc6a8602e30
This commit is contained in:
parent
2c9e113f8e
commit
2be921c88a
|
@ -65,7 +65,6 @@ NSString *const RCTUIManagerRootViewKey = @"RCTUIManagerRootViewKey";
|
||||||
|
|
||||||
// Animation
|
// Animation
|
||||||
RCTLayoutAnimationGroup *_layoutAnimationGroup; // Main thread only
|
RCTLayoutAnimationGroup *_layoutAnimationGroup; // Main thread only
|
||||||
NSMutableSet<UIView *> *_viewsToBeDeleted; // Main thread only
|
|
||||||
|
|
||||||
NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
|
NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
|
||||||
NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
|
NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
|
||||||
|
@ -156,8 +155,6 @@ RCT_EXPORT_MODULE()
|
||||||
_bridgeTransactionListeners = [NSMutableSet new];
|
_bridgeTransactionListeners = [NSMutableSet new];
|
||||||
_observerCoordinator = [RCTUIManagerObserverCoordinator new];
|
_observerCoordinator = [RCTUIManagerObserverCoordinator new];
|
||||||
|
|
||||||
_viewsToBeDeleted = [NSMutableSet new];
|
|
||||||
|
|
||||||
// Get view managers from bridge
|
// Get view managers from bridge
|
||||||
NSMutableDictionary *componentDataByName = [NSMutableDictionary new];
|
NSMutableDictionary *componentDataByName = [NSMutableDictionary new];
|
||||||
for (Class moduleClass in _bridge.moduleClasses) {
|
for (Class moduleClass in _bridge.moduleClasses) {
|
||||||
|
@ -695,8 +692,7 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(nonnull NSNumber *)containe
|
||||||
void (^completion)(BOOL) = ^(BOOL finished) {
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
||||||
completionsCalled++;
|
completionsCalled++;
|
||||||
|
|
||||||
[self->_viewsToBeDeleted removeObject:removedChild];
|
[removedChild removeFromSuperview];
|
||||||
[container removeReactSubview:removedChild];
|
|
||||||
|
|
||||||
if (animation.callback && completionsCalled == children.count) {
|
if (animation.callback && completionsCalled == children.count) {
|
||||||
animation.callback(@[@(finished)]);
|
animation.callback(@[@(finished)]);
|
||||||
|
@ -707,10 +703,20 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(nonnull NSNumber *)containe
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
[_viewsToBeDeleted addObject:removedChild];
|
// Hack: At this moment we have two contradict intents.
|
||||||
|
// First one: We want to delete the view from view hierarchy.
|
||||||
|
// Second one: We want to animate this view, which implies the existence of this view in the hierarchy.
|
||||||
|
// So, we have to remove this view from React's view hierarchy but postpone removing from UIKit's hierarchy.
|
||||||
|
// Here the problem: the default implementation of `-[UIView removeReactSubview:]` also removes the view from UIKit's hierarchy.
|
||||||
|
// So, let's temporary restore the view back after removing.
|
||||||
|
// To do so, we have to memorize original `superview` (which can differ from `container`) and an index of removed view.
|
||||||
|
UIView *originalSuperview = removedChild.superview;
|
||||||
|
NSUInteger *originalIndex = [originalSuperview.subviews indexOfObject:removedChild];
|
||||||
|
[container removeReactSubview:removedChild];
|
||||||
|
[originalSuperview insertSubview:removedChild atIndex:originalIndex];
|
||||||
|
|
||||||
// Disable user interaction while the view is animating since JS won't receive
|
// Disable user interaction while the view is animating
|
||||||
// the view events anyway.
|
// since the view is (conseptually) deleted and not supposed to be interactive.
|
||||||
removedChild.userInteractionEnabled = NO;
|
removedChild.userInteractionEnabled = NO;
|
||||||
|
|
||||||
NSString *property = deletingLayoutAnimation.property;
|
NSString *property = deletingLayoutAnimation.property;
|
||||||
|
@ -862,6 +868,7 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag
|
||||||
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
||||||
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
|
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
|
for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
|
||||||
id<RCTComponent> view = registry[addChildReactTags[index]];
|
id<RCTComponent> view = registry[addChildReactTags[index]];
|
||||||
if (view) {
|
if (view) {
|
||||||
|
@ -872,22 +879,8 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag
|
||||||
NSArray<NSNumber *> *sortedIndices =
|
NSArray<NSNumber *> *sortedIndices =
|
||||||
[destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
[destinationsToChildrenToAdd.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||||
for (NSNumber *reactIndex in sortedIndices) {
|
for (NSNumber *reactIndex in sortedIndices) {
|
||||||
NSInteger insertAtIndex = reactIndex.integerValue;
|
|
||||||
|
|
||||||
// When performing a delete animation, views are not removed immediately
|
|
||||||
// from their container so we need to offset the insertion index if a view
|
|
||||||
// that will be removed appears earlier than the view we are inserting.
|
|
||||||
if (isUIViewRegistry && _viewsToBeDeleted.count > 0) {
|
|
||||||
for (NSInteger index = 0; index < insertAtIndex; index++) {
|
|
||||||
UIView *subview = ((UIView *)container).reactSubviews[index];
|
|
||||||
if ([_viewsToBeDeleted containsObject:subview]) {
|
|
||||||
insertAtIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex]
|
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex]
|
||||||
atIndex:insertAtIndex];
|
atIndex:reactIndex.integerValue];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue