You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 17 iPad应用模态弹窗崩溃求助(含keyWindow废弃问题)

问题分析与修复方案

核心崩溃原因

  • 自呈现错误:快速连续开关弹窗时,前一个VolumeVC未完全 dismiss,原topViewController方法错误将其识别为顶层控制器,导致用VolumeVC自身呈现新实例,触发Application tried to present modal view controller on itself崩溃。
  • 顶层控制器获取逻辑缺陷:原方法仅遍历presentedViewController,未处理UITabBarController、UISplitViewController这类容器控制器的嵌套结构,iOS17视图生命周期变化下易拿到错误控制器。
  • sourceView为空问题:替换keyWindow后未正确获取活跃窗口,或传入的view已处于不可用状态,导致UIPopoverPresentationController的sourceView为nil。

具体修复步骤

1. 修复顶层控制器获取逻辑

替换原方法,处理容器控制器嵌套,排除正在dismiss的控制器:

+ (UIViewController*)topViewController {
    UIWindow *window = [self getActiveWindow];
    if (!window) return nil;
    
    UIViewController *topVC = window.rootViewController;
    while (true) {
        // 处理UITabBarController
        if ([topVC isKindOfClass:[UITabBarController class]]) {
            topVC = [(UITabBarController *)topVC selectedViewController];
        }
        // 处理UISplitViewController
        else if ([topVC isKindOfClass:[UISplitViewController class]]) {
            topVC = [(UISplitViewController *)topVC viewControllers].lastObject;
        }
        // 处理presentedViewController,跳过正在dismiss的实例
        if (topVC.presentedViewController && !topVC.presentedViewController.isBeingDismissed) {
            topVC = topVC.presentedViewController;
        } else {
            break;
        }
    }
    return topVC;
}

// 适配iOS13+的活跃窗口获取
+ (UIWindow*)getActiveWindow {
    UIWindow *activeWindow = nil;
    if (@available(iOS 13.0, *)) {
        for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) {
            if (scene.activationState == UISceneActivationStateForegroundActive) {
                activeWindow = scene.windows.firstObject;
                break;
            }
        }
    } else {
        activeWindow = [UIApplication sharedApplication].keyWindow;
    }
    return activeWindow;
}

2. 修正弹窗展示方法的逻辑漏洞

移除冗余代码,增加安全校验:

+ (void)showDialogiPad:(UIViewController *)contentVC
       fromPresentingVC:(UIViewController *)presentingVC
                 inView:(UIView *)view
                atFrame:(CGRect)frame
          withDirection:(UIPopoverArrowDirection)direction {
    // 基础参数校验
    if (!contentVC || !presentingVC || contentVC == presentingVC) {
        NSLog(@"Invalid popover presentation parameters");
        return;
    }
    // 校验sourceView有效性
    if (!view || !view.window) {
        NSLog(@"Source view is not in window hierarchy");
        return;
    }

    contentVC.modalPresentationStyle = UIModalPresentationPopover;
    
    UIPopoverPresentationController *popoverPC = contentVC.popoverPresentationController;
    popoverPC.sourceRect = frame;
    popoverPC.sourceView = view;
    popoverPC.permittedArrowDirections = direction;
    
    // 确保presentingVC处于可呈现状态
    if (!presentingVC.isBeingDismissed && presentingVC.view.window) {
        [presentingVC presentViewController:contentVC animated:YES completion:nil];
    }
}

3. 调用阶段的额外防护

避免重复呈现未关闭的弹窗,可添加实例跟踪逻辑:

static VolumeVC *_currentVolumeVC;

- (void)triggerVolumePopover {
    // 若当前弹窗未关闭,先dismiss再重新展示
    if (_currentVolumeVC && _currentVolumeVC.presentingViewController) {
        [_currentVolumeVC dismissViewControllerAnimated:YES completion:^{
            _currentVolumeVC = nil;
            [self triggerVolumePopover];
        }];
        return;
    }
    
    _currentVolumeVC = [[VolumeVC alloc] init];
    [Utility showDialogiPad:_currentVolumeVC
           fromPresentingVC:[Utility topViewController]
                     inView:self.view
                    atFrame:self.volumeBtn.frame
              withDirection:UIPopoverArrowDirectionAny];
}

验证要点

  • 快速连续点击弹窗开关,确认无自呈现崩溃;
  • 全场景测试弹窗,确认无non-nil sourceView错误;
  • 验证iOS15与iOS17版本的兼容性。

内容的提问来源于stack exchange,提问作者Silverness

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 10:19:56