Swift中点击按钮无法跳转至ViewController1(含侧边栏)问题排查
问题:点击按钮无法导航至带侧边菜单的ViewController1
我已在Storyboard中为ViewController添加NavigationControllerScene并将其设为初始视图控制器。点击testBtn时无法导航至ViewController1——需求是展示带有侧边菜单和底部TabBar的ViewController,我编写了相关代码,且在AppDelegate与SceneDelegate中添加了相同代码,附上代码如下:
ViewController代码
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setUpNavigationBar(with: "Viewcontroller", isBackNeed: false) } @IBAction func testBtn(_ sender: UIButton){ // added same code in Appdelegate and scenedelagete as well let home = Helper.getVcObject(vcName: .ViewController1, StoryBoardName: .Main) as! ViewController1 let menu = Helper.getVcObject(vcName: .MenuViewController, StoryBoardName: .Main) as! MenuViewController let nav = MainNavigationController(rootViewController: home) let sideMenu = SideMenuController(contentViewController: nav, menuViewController: menu) Helper.replaceRootView(for: sideMenu) } }
自定义Helper代码
let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows .filter({$0.isKeyWindow}).first enum StoryBoardNameCase: String { case Main = "Main" } enum VCNameCase: String { case MenuViewController = "MenuViewController" case ViewController1 = "ViewController1" } class Helper { static func getVcObject(vcName:VCNameCase, StoryBoardName:StoryBoardNameCase) -> UIViewController{ let storyBoard: UIStoryboard = UIStoryboard(name: StoryBoardName.rawValue, bundle: nil) let vc = storyBoard.instantiateViewController(withIdentifier: vcName.rawValue) return vc } static func replaceRootView(for rootViewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) { if animated { UIView.transition(with: keyWindow ?? UIWindow(), duration: 0.5, options: .transitionCrossDissolve, animations: { let oldState: Bool = UIView.areAnimationsEnabled UIView.setAnimationsEnabled(false) keyWindow?.rootViewController = rootViewController keyWindow?.makeKeyAndVisible() UIView.setAnimationsEnabled(oldState) }, completion: { (finished: Bool) -> () in completion?() }) } else { keyWindow?.rootViewController = rootViewController keyWindow?.makeKeyAndVisible() } }
AppDelegate代码(SceneDelegate中代码相同)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. let home = Helper.getVcObject(vcName: .ViewController1, StoryBoardName: .Main) as! ViewController1 let menu = Helper.getVcObject(vcName: .MenuViewController, StoryBoardName: .Main) as! MenuViewController let nav = MainNavigationController(rootViewController: home) let sideMenu = SideMenuController(contentViewController: nav, menuViewController: menu) Helper.replaceRootView(for: sideMenu) SideMenuController.preferences.basic.menuWidth = UIScreen.main.bounds.width * 0.8 return true }
排查与修复方案
1. 核心问题:初始窗口被提前替换
你在AppDelegate和SceneDelegate中都执行了替换根VC的代码,iOS 13+启动时会同时触发这两个文件的初始化逻辑,导致Storyboard中设置的初始NavigationController(带ViewController)还没来得及展示,就被替换成了SideMenuController。也就是说,你点击testBtn的时候,当前页面根本不是你预期的ViewController,按钮事件自然不会生效。
修复:只保留SceneDelegate中的根VC替换代码(适配iOS 13+),如果需要兼容iOS 12及以下,用条件编译区分:
// SceneDelegate.swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) let home = Helper.getVcObject(vcName: .ViewController1, StoryBoardName: .Main) as! ViewController1 let menu = Helper.getVcObject(vcName: .MenuViewController, StoryBoardName: .Main) as! MenuViewController let nav = MainNavigationController(rootViewController: home) let sideMenu = SideMenuController(contentViewController: nav, menuViewController: menu) window.rootViewController = sideMenu window.makeKeyAndVisible() self.window = window SideMenuController.preferences.basic.menuWidth = UIScreen.main.bounds.width * 0.8 } // AppDelegate.swift(仅iOS 12及以下生效) @available(iOS, obsoleted: 13.0) func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let window = UIWindow(frame: UIScreen.main.bounds) let home = Helper.getVcObject(vcName: .ViewController1, StoryBoardName: .Main) as! ViewController1 let menu = Helper.getVcObject(vcName: .MenuViewController, StoryBoardName: .Main) as! MenuViewController let nav = MainNavigationController(rootViewController: home) let sideMenu = SideMenuController(contentViewController: nav, menuViewController: menu) window.rootViewController = sideMenu window.makeKeyAndVisible() SideMenuController.preferences.basic.menuWidth = UIScreen.main.bounds.width * 0.8 return true }
2. KeyWindow获取逻辑失效
全局常量keyWindow只会在初始化时获取一次,App运行中窗口状态变化(比如Scene切换)会导致它变成nil,进而导致replaceRootView方法无法真正替换根VC。
修复:把KeyWindow的获取逻辑移到replaceRootView方法内部,每次调用都获取最新的活跃窗口:
class Helper { static func getVcObject(vcName:VCNameCase, StoryBoardName:StoryBoardNameCase) -> UIViewController{ let storyBoard: UIStoryboard = UIStoryboard(name: StoryBoardName.rawValue, bundle: nil) return storyBoard.instantiateViewController(withIdentifier: vcName.rawValue) } static func replaceRootView(for rootViewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) { // 每次调用都重新获取当前活跃的KeyWindow let keyWindow = UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .compactMap({$0 as? UIWindowScene}) .first?.windows .filter({$0.isKeyWindow}).first guard let window = keyWindow else { print("无法获取当前活跃窗口") return } if animated { UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: { let oldState = UIView.areAnimationsEnabled UIView.setAnimationsEnabled(false) window.rootViewController = rootViewController window.makeKeyAndVisible() UIView.setAnimationsEnabled(oldState) }, completion: { _ in completion?() }) } else { window.rootViewController = rootViewController window.makeKeyAndVisible() } } }
3. 其他验证点
- 检查Storyboard中ViewController1、MenuViewController的Storyboard ID是否和枚举值完全一致(大小写、拼写不能错),否则实例化会失败;
- 把
as!强制转换改成as?并添加判断,避免崩溃:guard let home = Helper.getVcObject(vcName: .ViewController1, StoryBoardName: .Main) as? ViewController1, let menu = Helper.getVcObject(vcName: .MenuViewController, StoryBoardName: .Main) as? MenuViewController else { print("VC实例化失败") return }
内容的提问来源于stack exchange,提问作者Swift
相关产品推荐
相关产品推荐

