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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 08:54:50