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

UINavigationItem扩展中弹出UIAlertController的实现问题

你遇到的核心问题是 UINavigationItem 本身不是控制器类型,没有 present(_:animated:completion:) 方法,所以无法直接在它的扩展方法里弹出弹窗,同时你原有代码中的 UIAlertController 初始化缺少必填的 preferredStyle 参数,运行时会触发崩溃,下面提供两种可直接落地的实现方案:


方案1:扩展内获取顶层控制器弹出弹窗

不需要改动原有调用逻辑,只需要补充获取当前应用最顶层显示控制器的工具方法,拿到控制器后直接弹出alert即可,修改后的完整扩展代码如下:

extension UINavigationItem {
    func barButton() {
        let barButton = UIBarButtonItem()
        barButton.image = UIImage(systemName: "person.fill")
        barButton.tintColor = .white
        barButton.action = #selector(barButtonClicked)
        barButton.target = self
        self.rightBarButtonItem = barButton
    }
    
    // 工具方法:适配iOS13+ 获取当前顶层显示的控制器
    private func getTopViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .compactMap { $0 as? UIWindowScene }
            .first?.windows
            .first { $0.isKeyWindow }
        
        var topController = keyWindow?.rootViewController
        while let presentedController = topController?.presentedViewController {
            topController = presentedController
        }
        return topController
    }

    @objc func barButtonClicked() {
        // 补上必填的preferredStyle参数,可选.alert或.actionSheet
        let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        // 补充取消按钮避免弹窗无法关闭
        alertController.addAction(UIAlertAction(title: "取消", style: .cancel))
        
        let logoutAction = UIAlertAction(title: "Log Out", style: .destructive) { _ in
            print("logout")
        }
        alertController.addAction(logoutAction)
        
        let historyAction = UIAlertAction(title: "History", style: .default) { _ in
            print("history")
        }
        alertController.addAction(historyAction)
        
        // 调用顶层控制器弹出弹窗
        guard let topVC = getTopViewController() else { return }
        topVC.present(alertController, animated: true)
    }
}

调用方式和你原有逻辑完全一致,在各控制器的 viewDidLoad 里调用 self.navigationItem.barButton() 即可正常使用。


方案2:回调透传点击事件给所属控制器

如果不同控制器需要实现不同的点击逻辑,推荐用回调把处理权交给调用方,灵活性更高,实现代码如下:

import ObjectiveC

extension UINavigationItem {
    // 用关联对象存储回调闭包
    private var barButtonAction: (() -> Void)? {
        get {
            objc_getAssociatedObject(self, &AssociatedKeys.actionKey) as? () -> Void
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.actionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    private struct AssociatedKeys {
        static var actionKey = "barButtonActionKey"
    }
    
    func setupBarButton(action: @escaping () -> Void) {
        self.barButtonAction = action
        let barButton = UIBarButtonItem()
        barButton.image = UIImage(systemName: "person.fill")
        barButton.tintColor = .white
        barButton.action = #selector(barButtonClicked)
        barButton.target = self
        self.rightBarButtonItem = barButton
    }
    
    @objc private func barButtonClicked() {
        barButtonAction?()
    }
}

调用时直接在对应控制器里处理弹窗逻辑即可:

// 视图控制器的viewDidLoad中调用
self.navigationItem.setupBarButton { [weak self] in
    guard let self = self else { return }
    let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
    alertController.addAction(UIAlertAction(title: "取消", style: .cancel))
    alertController.addAction(UIAlertAction(title: "Log Out", style: .destructive) { _ in
        print("logout")
    })
    alertController.addAction(UIAlertAction(title: "History", style: .default) { _ in
        print("history")
    })
    self.present(alertController, animated: true)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 00:36:00