iOS设备旋转时UINavigationController意外弹出问题求助
问题:UISplitViewController折叠模式下旋转导致导航控制器意外Pop(iOS 16,iPhone 11/Xr专属)
问题细节
- 影响设备:仅iPhone 11、iPhone Xr的模拟器及真机,X、XS、11Pro、12、13Pro、14Pro等机型无法复现
- 系统版本:所有测试基于iOS 16
- 触发场景:当折叠模式下的UISplitViewController主控制器为UINavigationController时,设备旋转会导致导航控制器意外执行pop操作——该pop并非正常用户触发,且
isMovingFromParent不会被设为true,仅会将导航栈返回到上一控制器 - SplitVC配置:采用经典界面(未使用iOS14+列布局)
- 断点验证:已在
[UINavigationController popViewControllerAnimated:]设置断点确认问题,触发时的调用栈如下:
* 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
开发者反馈及解决办法
开发者反馈情况
确实有不少开发者在技术社区反馈过该问题,属于iOS 16针对iPhone 11/Xr这类特定屏幕尺寸机型的UIKit内部bug,根源是SplitViewController在旋转时错误触发了导航控制器的分离逻辑,导致意外pop。
可行解决办法
自定义导航控制器拦截异常pop
继承UINavigationController,通过检查调用栈识别SplitVC触发的异常pop并拦截:class SafeNavigationController: UINavigationController { override func popViewController(animated: Bool) -> UIViewController? { // 检查调用栈是否包含SplitVC相关的私有方法,判断是否为异常触发 let isSplitInitiated = Thread.callStackSymbols.contains { $0.contains("_separateViewControllersAfterAndIncludingViewController:forSplitViewController:") } if isSplitInitiated && viewControllers.count > 1 { // 拦截异常pop,返回nil return nil } return super.popViewController(animated: animated) } }注意:调用栈检查依赖私有方法符号,需做好兼容性测试。
通过SplitVC代理阻止过渡中的分离操作
实现UISplitViewControllerDelegate,在旋转过渡中返回nil阻止SecondaryViewController分离:extension YourSplitVC: UISplitViewControllerDelegate { func splitViewController(_ svc: UISplitViewController, separateSecondaryViewControllerFrom primaryViewController: UIViewController) -> UIViewController? { // 处于旋转过渡时阻止分离 if svc.isTransitioning { return nil } // 正常场景返回SecondaryVC逻辑不变 return (primaryViewController as? UINavigationController)?.topViewController?.presentedViewController } }升级系统版本
Apple在iOS 16.4及后续版本中修复了该类SplitVC布局bug,若允许用户升级系统,可优先选择此方案。
内容的提问来源于stack exchange,提问作者Rudedog
相关产品推荐
相关产品推荐

