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

