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

iOS开发:类外部调用类方法失败,上下文菜单视图不显示如何解决?

问题:外部调用导航控制器方法无法显示上下文菜单

我尝试用以下代码实现导航栏按钮触发上下文菜单,但无法正常显示child视图:

var buttonNumber = 0

func settings() {
    CNavigationViewController().contextMenu()
}

class CNavigationViewController: UINavigationController {

...

@objc func rightbuttonAction(sender: UIButton!) {
    buttonNumber = sender.tag
    settings()
}

func contextMenu() {
    
    print(buttonNumber)
    
    if pressed == true { pressed = false
        
        child.willMove(toParentViewController: nil)
        child.view.removeFromSuperview()
        child.removeFromParentViewController()
        visualEffectView.removeFromSuperview()
        
        for index in 1...rightBarButtonsCount {
            if index == buttonNumber { continue }
            rightStackView.arrangedSubviews[index-1].alpha = 1
        }
        
    } else { pressed = true
        
        visualEffectView.frame = UIScreen.main.bounds
        navigationView.addSubview(visualEffectView)
        navigationView.sendSubview(toBack: visualEffectView)
        addChildViewController(child)
        
        guard let childView = child.view else { fatalError("error") }
        view.addSubview(childView)
        child.didMove(toParentViewController: self)
            
        childView.translatesAutoresizingMaskIntoConstraints = false
        childView.topAnchor.constraint(equalTo: rightStackView.arrangedSubviews[buttonNumber-1].topAnchor, constant: rightStackView.arrangedSubviews[buttonNumber-1].frame.size.height + 8).isActive = true
        childView.trailingAnchor.constraint(equalTo: rightStackView.arrangedSubviews[buttonNumber-1].trailingAnchor).isActive = true
        
        for index in 1...rightBarButtonsCount {
            if index == buttonNumber { continue }
            rightStackView.arrangedSubviews[index-1].alpha = 0
        }
    }
}
}

但如果直接在按钮点击方法里调用contextMenu(),就能正常显示菜单:

@objc func rightbuttonAction(sender: SLButton!) {
    buttonNumber = sender.tag
    contextMenu() // 替换为直接调用,而非settings()
}

请问为什么第一种方式无法显示child视图?如何实现类外部调用该函数?


更新:App启动代码

SceneDelegate

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    if let windowScene = (scene as? UIWindowScene) {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = CLoadingViewController()
        self.window = window
        window.makeKeyAndVisible()
    }
}

CLoadingViewController

class CLoadingViewController: UIViewController {

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    mainScreen()
}

func mainScreen() {
    let navigationView = CNavigationViewController()
    let mainView = CStoreViewController()
    navigationView.navigationBar.frame.size = navigationView.navigationBar.sizeThatFits(CGSize(width: navigationView.navigationBar.frame.size.width, height: 2))
    navigationView.viewControllers = [mainView]
    present(navigationView, animated: false, completion: nil)
}
}

原因分析

核心问题出在settings()函数里:你每次调用CNavigationViewController().contextMenu()时,都会创建一个全新的CNavigationViewController实例,这个新实例从未被添加到App的视图层级中(没有被present或push),所以它的contextMenu()操作的是一个不存在于屏幕上的控制器,自然看不到任何界面变化。

而直接在rightbuttonAction里调用contextMenu(),用的是当前正在显示的CNavigationViewController自身实例,操作的是已经在屏幕上的视图,因此能正常触发菜单显示。


解决方案

方案一:获取当前活跃的导航控制器实例

通过UIKit的视图层级找到当前正在显示的CNavigationViewController,再调用其方法:

func settings() {
    // 从当前窗口的根控制器开始查找
    guard let rootVC = UIApplication.shared.windows.first?.rootViewController else { return }
    // 找到被present的导航控制器实例
    if let navVC = rootVC.presentedViewController as? CNavigationViewController {
        navVC.contextMenu()
    }
}

注意:这种方式依赖当前的视图层级结构,如果后续页面跳转逻辑变化,可能会失效。

方案二:使用单例模式管理导航控制器(适合全局唯一场景)

将CNavigationViewController改为单例,确保全局只有一个实例:

class CNavigationViewController: UINavigationController {
    // 定义单例
    static let shared = CNavigationViewController()
    
    // ... 原有代码保持不变
}

然后在启动代码中使用单例:

func mainScreen() {
    let navigationView = CNavigationViewController.shared
    let mainView = CStoreViewController()
    navigationView.navigationBar.frame.size = navigationView.navigationBar.sizeThatFits(CGSize(width: navigationView.navigationBar.frame.size.width, height: 2))
    navigationView.viewControllers = [mainView]
    present(navigationView, animated: false, completion: nil)
}

之后settings()函数就可以直接调用单例的方法:

func settings() {
    CNavigationViewController.shared.contextMenu()
}

方案三:通过闭包实现解耦调用(推荐)

在CNavigationViewController中定义闭包属性,外部通过闭包触发操作,避免全局依赖:

class CNavigationViewController: UINavigationController {
    // 定义触发菜单的闭包
    var triggerContextMenu: (() -> Void)?
    
    @objc func rightbuttonAction(sender: UIButton!) {
        buttonNumber = sender.tag
        // 触发闭包
        triggerContextMenu?()
    }
    
    // ... 原有代码保持不变
}

在创建导航控制器的地方绑定闭包:

func mainScreen() {
    let navigationView = CNavigationViewController()
    let mainView = CStoreViewController()
    navigationView.navigationBar.frame.size = navigationView.navigationBar.sizeThatFits(CGSize(width: navigationView.navigationBar.frame.size.width, height: 2))
    navigationView.viewControllers = [mainView]
    
    // 绑定闭包,直接调用当前实例的contextMenu
    navigationView.triggerContextMenu = { [weak navigationView] in
        navigationView?.contextMenu()
    }
    
    present(navigationView, animated: false, completion: nil)
}

如果一定要保留settings()全局函数,可以将闭包引用存储到全局变量中,但不推荐这种方式,尽量减少全局状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 11:24:53