iOS does an unwanted pop of the navigation controller when the phone rotates

51 Views Asked by At

This is an issue that affects the iPhone 11 and the iPhone Xr on both the simulator and on physical devices.

I also tested with the X, XS, 11Pro, 12, 13Pro and 14Pro (some on real devices, some on simulators) but was unable to replicate it on those devices.

All tests were done with the iOS 16.

Basically, if I have a split view controller in collapsed mode and the split VC's primary VC is a navigation controller, then when the user rotates the phone, the navigation controller gets popped, but not a normal pop which sets isMovingFromParent to true. It just pops it and returns the navigation controller to the previous view controller on the stack.

The split VC is running with the classic interface (i.e., not running the iOS 14+ column-style layout stuff).

I set a breakpoint on [UINavigationController popViewControllerAnimated:] and can confirm that is what is happening. The stack trace when I hit the break point is

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
    frame #0: 0x0000000108e28434 UIKitCore`-[UINavigationController popViewControllerAnimated:]
    frame #1: 0x0000000108e2faa4 UIKitCore`-[UINavigationController _separateViewControllersAfterAndIncludingViewController:forSplitViewController:] + 284
    frame #2: 0x0000000108e2699c UIKitCore`-[UINavigationController separateSecondaryViewControllerForSplitViewController:] + 24
  * frame #3: 0x0000000108ea36fc UIKitCore`-[UISplitViewControllerPanelImpl panelController:unspecifiedStyleSeparateSecondaryViewControllerFromPrimaryViewController:] + 428
    frame #4: 0x0000000108ea2a0c UIKitCore`-[UISplitViewControllerPanelImpl panelController:separateSecondaryViewControllerFromPrimaryViewController:] + 344
    frame #5: 0x0000000108e6edac UIKitCore`__54-[UIPanelController _expandWithTransitionCoordinator:]_block_invoke_4 + 84
    frame #6: 0x0000000108e70da8 UIKitCore`+[UIPanelController _withDisabledAppearanceTransitions:forVisibleDescendantsOf:perform:] + 436
    frame #7: 0x0000000108e707e0 UIKitCore`-[UIPanelController _withDisabledAppearanceTransitionsPerform:] + 132
    frame #8: 0x0000000108e6ed38 UIKitCore`__54-[UIPanelController _expandWithTransitionCoordinator:]_block_invoke_3 + 104
    frame #9: 0x0000000108ec736c UIKitCore`+[UIViewController _performWithoutDeferringTransitionsAllowingAnimation:actions:] + 132
    frame #10: 0x0000000108e6ecb0 UIKitCore`__54-[UIPanelController _expandWithTransitionCoordinator:]_block_invoke_2 + 112
    frame #11: 0x00000001099ec138 UIKitCore`+[UIView(Animation) performWithoutAnimation:] + 68
    frame #12: 0x0000000108e6ec1c UIKitCore`__54-[UIPanelController _expandWithTransitionCoordinator:]_block_invoke + 152
    frame #13: 0x0000000108edfb90 UIKitCore`-[_UIViewControllerTransitionCoordinator _applyBlocks:releaseBlocks:] + 228
    frame #14: 0x0000000108edd29c UIKitCore`-[_UIViewControllerTransitionContext __runAlongsideAnimations] + 212
    frame #15: 0x00000001099ed584 UIKitCore`+[UIView _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 500
    frame #16: 0x00000001099ed96c UIKitCore`+[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:] + 44
    frame #17: 0x0000000108ef226c UIKitCore`__58-[_UIWindowRotationAnimationController animateTransition:]_block_invoke_2 + 220
    frame #18: 0x00000001099f0bdc UIKitCore`+[UIView _performBlockDelayingTriggeringResponderEvents:forScene:] + 168
    frame #19: 0x0000000108ef208c UIKitCore`__58-[_UIWindowRotationAnimationController animateTransition:]_block_invoke + 140
    frame #20: 0x00000001099ee6b0 UIKitCore`+[UIView(UIViewAnimationWithBlocksPrivate) _modifyAnimationsWithPreferredFrameRateRange:updateReason:animations:] + 164
    frame #21: 0x00000001099ed584 UIKitCore`+[UIView _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 500
    frame #22: 0x00000001099ed96c UIKitCore`+[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:] + 44
    frame #23: 0x0000000108ef1ef4 UIKitCore`-[_UIWindowRotationAnimationController animateTransition:] + 448
    frame #24: 0x0000000109591cfc UIKitCore`-[UIWindow _rotateToBounds:withAnimator:transitionContext:] + 500
    frame #25: 0x0000000109594154 UIKitCore`-[UIWindow _rotateWindowToOrientation:updateStatusBar:duration:skipCallbacks:] + 1092
    frame #26: 0x0000000109594594 UIKitCore`-[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 320
    frame #27: 0x0000000109590f80 UIKitCore`-[UIWindow _internal_setRotatableViewOrientation:updateStatusBar:duration:force:] + 104
    frame #28: 0x0000000109592c18 UIKitCore`__57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 188
    frame #29: 0x000000010959296c UIKitCore`-[UIWindow _updateToInterfaceOrientation:duration:force:] + 844
    frame #30: 0x0000000108cabc04 UIKitCore`-[_UIScenefbsSceneBasedMetricsCalculator _updateMetricsOnWindows:animated:] + 1004
    frame #31: 0x0000000109788588 UIKitCore`-[UIWindowScene _computeMetricsForWindows:animated:] + 84
    frame #32: 0x0000000109787b68 UIKitCore`__55-[UIWindowScene _computeMetrics:withTransitionContext:]_block_invoke + 100
    frame #33: 0x0000000109787ca8 UIKitCore`-[UIWindowScene _computeTraitCollectionAndCoordinateSpaceForcingDelegateCallback:withAction:] + 288
    frame #34: 0x0000000109787af8 UIKitCore`-[UIWindowScene _computeMetrics:withTransitionContext:] + 76
    frame #35: 0x0000000109784ca8 UIKitCore`-[UIWindowScene _computeMetricsAndCrossFadeInLiveResize:withTransitionContext:] + 204
    frame #36: 0x00000001099ed584 UIKitCore`+[UIView _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 500
    frame #37: 0x00000001099ed7b0 UIKitCore`+[UIView(UIViewAnimationWithBlocks) _animateWithDuration:delay:options:factory:animations:completion:] + 48
    frame #38: 0x000000010908d618 UIKitCore`+[BSAnimationSettings(UIKit) tryAnimatingWithSettings:fromCurrentState:actions:completion:] + 468
    frame #39: 0x000000010918d0d4 UIKitCore`_UISceneSettingsDiffActionPerformChangesWithTransitionContextAndCompletion + 196
    frame #40: 0x0000000108cb0aa8 UIKitCore`-[_UIWindowSceneGeometrySettingsDiffAction _updateSceneGeometryWithSettingObserverContext:windowScene:transitionContext:] + 764
    frame #41: 0x0000000108cb0718 UIKitCore`-[_UIWindowSceneGeometrySettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] + 1084
    frame #42: 0x0000000108b20b3c UIKitCore`__64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.196 + 552
    frame #43: 0x0000000108b1f93c UIKitCore`-[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] + 208
    frame #44: 0x0000000108b207f8 UIKitCore`-[UIScene scene:didUpdateWithDiff:transitionContext:completion:] + 220
    frame #45: 0x00000001090b35d8 UIKitCore`-[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] + 308
    frame #46: 0x0000000184e593d4 FrontBoardServices`-[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] + 392
    frame #47: 0x0000000184e810d0 FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 + 124
    frame #48: 0x0000000184e64704 FrontBoardServices`-[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 160
    frame #49: 0x0000000184e8101c FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke + 312
    frame #50: 0x0000000105ccdd50 libdispatch.dylib`_dispatch_client_callout + 16
    frame #51: 0x0000000105cd1968 libdispatch.dylib`_dispatch_block_invoke_direct + 392
    frame #52: 0x0000000184e9ef14 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 44
    frame #53: 0x0000000184e9ee08 FrontBoardServices`-[FBSSerialQueue _targetQueue_performNextIfPossible] + 176
    frame #54: 0x0000000184e9ef48 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 24
    frame #55: 0x000000018039ac6c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #56: 0x000000018039abb4 CoreFoundation`__CFRunLoopDoSource0 + 172
    frame #57: 0x000000018039a324 CoreFoundation`__CFRunLoopDoSources0 + 232
    frame #58: 0x0000000180394958 CoreFoundation`__CFRunLoopRun + 748
    frame #59: 0x0000000180394254 CoreFoundation`CFRunLoopRunSpecific + 584
    frame #60: 0x0000000188eb7c9c GraphicsServices`GSEventRunModal + 160
    frame #61: 0x0000000109552ff0 UIKitCore`-[UIApplication _run] + 868
    frame #62: 0x0000000109556f3c UIKitCore`UIApplicationMain + 124
    frame #63: 0x000000010412ac20 aast`main at AppDelegate.swift:4:7
    frame #64: 0x00000001042bd514 dyld_sim`start_sim + 20
    frame #65: 0x000000010444df28 dyld`start + 2236

I'm wondering if anybody else has encountered this and if so, is there a workaround?

1

There are 1 best solutions below

1
Rudedog On

@HangarRash pointed me in the right direction, in that those two devices (and probably a few others) present as .regular size class in landscape, which causes the split view controller to separate into two view controllers. When the primary VC is a navigation controller, its default behavior is to pop its stack and use the popped VC as the secondary VC.

I solved the issue by overriding the primary navigation controller's separateSecondaryViewController(for:) method so it returned self. This seemed to work, but due to my lack of familiarity with the nuances of split view controllers I don't know if this is the correct solution or if there is a better one.