diff --git a/ios/ScreenAnimationController.mm b/ios/ScreenAnimationController.mm index f2ab3dd520..8e95bc118a 100644 --- a/ios/ScreenAnimationController.mm +++ b/ios/ScreenAnimationController.mm @@ -17,7 +17,40 @@ @implementation ScreenAnimationController { id _transitionContext; SharedElementAnimator *_sharedElementAnimator; BOOL _animate; - CGFloat _duration; + BOOL _observingMounts; + CGFloat _duration; +} + +- (void)startObservingMounts { + if (_observingMounts) { + return; + } + _observingMounts = YES; +#ifdef RCT_NEW_ARCH_ENABLED + if (_host != nil) { + [_host.surfacePresenter addObserver:self]; + } else { + [_bridge.uiManager.observerCoordinator addObserver:self]; + } +#else + [_bridge.uiManager.observerCoordinator addObserver:self]; +#endif +} + +- (void)stopObservingMounts { + if (!_observingMounts) { + return; + } + _observingMounts = NO; +#ifdef RCT_NEW_ARCH_ENABLED + if (_host != nil) { + [_host.surfacePresenter removeObserver:self]; + } else { + [_bridge.uiManager.observerCoordinator removeObserver:self]; + } +#else + [_bridge.uiManager.observerCoordinator removeObserver:self]; +#endif } - (instancetype)initWithContentTransition:(RNNEnterExitAnimation *)contentTransition @@ -53,25 +86,17 @@ - (instancetype)initWithContentTransition:(RNNEnterExitAnimation *)contentTransi #endif - (void)animateTransition:(id)transitionContext { -#ifdef RCT_NEW_ARCH_ENABLED - if (_host != nil) { - [_host.surfacePresenter addObserver:self]; - } else { - [_bridge.uiManager.observerCoordinator addObserver:self]; - } -#else - [_bridge.uiManager.observerCoordinator addObserver:self]; -#endif - - _animate = YES; - _transitionContext = transitionContext; - [self prepareTransitionContext:transitionContext]; - - UIViewController *fromVC = - [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; - if (![fromVC.navigationController.childViewControllers containsObject:fromVC]) { - [self performAnimationOnce]; - } + _animate = YES; + _transitionContext = transitionContext; + [self prepareTransitionContext:transitionContext]; + + UIViewController *fromVC = + [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; + if (![fromVC.navigationController.childViewControllers containsObject:fromVC]) { + [self performAnimationOnce]; + } else { + [self startObservingMounts]; + } } - (void)prepareTransitionContext:(id)transitionContext { @@ -112,8 +137,9 @@ - (NSArray *)createTransitionsFromVC:(UIViewController *)fromVC - (void)performAnimationOnce { if (_animate) { _animate = NO; - RCTExecuteOnMainQueue(^{ - id transitionContext = self->_transitionContext; + [self stopObservingMounts]; + RCTExecuteOnMainQueue(^{ + id transitionContext = self->_transitionContext; UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = @@ -122,8 +148,8 @@ - (void)performAnimationOnce { toVC:toVC containerView:transitionContext.containerView]; [self animateTransitions:transitions andTransitioningContext:transitionContext]; - }); - } + }); + } } - (void)animateTransitions:(NSArray> *)animators @@ -192,9 +218,10 @@ - (void)uiManagerDidPerformMounting:(RCTUIManager *)manager { } - (void)animationEnded:(BOOL)transitionCompleted { - UIView *toView = [_transitionContext viewForKey:UITransitionContextToViewKey]; - UIView *fromView = [_transitionContext viewForKey:UITransitionContextFromViewKey]; - [_sharedElementAnimator animationEnded]; + [self stopObservingMounts]; + UIView *toView = [_transitionContext viewForKey:UITransitionContextToViewKey]; + UIView *fromView = [_transitionContext viewForKey:UITransitionContextFromViewKey]; + [_sharedElementAnimator animationEnded]; if (toView) { toView.layer.transform = CATransform3DIdentity; toView.alpha = 1.f; @@ -206,4 +233,8 @@ - (void)animationEnded:(BOOL)transitionCompleted { _sharedElementAnimator = nil; } +- (void)dealloc { + [self stopObservingMounts]; +} + @end diff --git a/playground/ios/NavigationTests/ScreenAnimationControllerTest.mm b/playground/ios/NavigationTests/ScreenAnimationControllerTest.mm new file mode 100644 index 0000000000..3b3319cc21 --- /dev/null +++ b/playground/ios/NavigationTests/ScreenAnimationControllerTest.mm @@ -0,0 +1,79 @@ +#import "ScreenAnimationController.h" +#import +#import + +#ifdef RCT_NEW_ARCH_ENABLED + +@interface RNNScreenAnimationSurfacePresenterSpy : NSObject + +@property(nonatomic, assign) NSUInteger addObserverCount; +@property(nonatomic, assign) NSUInteger removeObserverCount; + +@end + +@implementation RNNScreenAnimationSurfacePresenterSpy + +- (void)addObserver:(id)observer { + self.addObserverCount++; +} + +- (void)removeObserver:(id)observer { + self.removeObserverCount++; +} + +@end + +@interface RNNScreenAnimationHostSpy : NSObject + +@property(nonatomic, strong) RNNScreenAnimationSurfacePresenterSpy *surfacePresenter; + +@end + +@implementation RNNScreenAnimationHostSpy + +@end + +@interface ScreenAnimationControllerTest : XCTestCase + +@end + +@implementation ScreenAnimationControllerTest + +- (void)testAnimationEndRemovesMountObserver { + RNNScreenAnimationSurfacePresenterSpy *surfacePresenter = + [RNNScreenAnimationSurfacePresenterSpy new]; + RNNScreenAnimationHostSpy *host = [RNNScreenAnimationHostSpy new]; + host.surfacePresenter = surfacePresenter; + ScreenAnimationController *controller = + [[ScreenAnimationController alloc] initWithContentTransition:nil + elementTransitions:nil + sharedElementTransitions:nil + duration:0 + host:(RCTHost *)host]; + + UIViewController *fromViewController = [UIViewController new]; + UIViewController *toViewController = [UIViewController new]; + UINavigationController *navigationController = + [[UINavigationController alloc] initWithRootViewController:fromViewController]; + XCTAssertNotNil(navigationController); + + id transitionContext = OCMProtocolMock(@protocol(UIViewControllerContextTransitioning)); + OCMStub([transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]) + .andReturn(fromViewController); + OCMStub([transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]) + .andReturn(toViewController); + OCMStub([transitionContext viewForKey:UITransitionContextFromViewKey]).andReturn(nil); + OCMStub([transitionContext viewForKey:UITransitionContextToViewKey]).andReturn(nil); + + [controller animateTransition:transitionContext]; + [controller animateTransition:transitionContext]; + XCTAssertEqual(surfacePresenter.addObserverCount, 1); + + [controller animationEnded:NO]; + [controller animationEnded:NO]; + XCTAssertEqual(surfacePresenter.removeObserverCount, 1); +} + +@end + +#endif diff --git a/playground/ios/playground.xcodeproj/project.pbxproj b/playground/ios/playground.xcodeproj/project.pbxproj index 4e76a4d4b3..f137983a68 100644 --- a/playground/ios/playground.xcodeproj/project.pbxproj +++ b/playground/ios/playground.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7A8B9C0D1E2F304152637485 /* ScreenAnimationControllerTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A8B9C0D1E2F304152637486 /* ScreenAnimationControllerTest.mm */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -107,6 +108,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7A8B9C0D1E2F304152637486 /* ScreenAnimationControllerTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ScreenAnimationControllerTest.mm; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* playground.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = playground.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = ""; }; @@ -395,6 +397,7 @@ E58D261C238587F4003F36BA /* NavigationTests */ = { isa = PBXGroup; children = ( + 7A8B9C0D1E2F304152637486 /* ScreenAnimationControllerTest.mm */, E58D26442385888C003F36BA /* Options */, E58D262E2385888B003F36BA /* utils */, E58D26422385888C003F36BA /* RNNBasePresenterTest.mm */, @@ -959,6 +962,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7A8B9C0D1E2F304152637485 /* ScreenAnimationControllerTest.mm in Sources */, 5007B4392472D9A70002AA4E /* RNNNativeViewController.mm in Sources */, 5007B4382472D9A20002AA4E /* RNNCustomViewController.mm in Sources */, E58D265B2385888C003F36BA /* UIViewController+RNNOptionsTest.mm in Sources */,