
一. 引言
在刚创建一个 iOS 工程时,Xcode 会默认生成一个简单的 ViewController 并把它作为 App 启动后展示的第一个页面。但在真实项目中,我们往往需要:
- 使用 TabBar 作为首页
- 使用导航控制器包裹首页
- 动态选择首页(引导页 / 登录页 / 主页面)
- 使用自己的 AppDelegate 完成初始化逻辑(路由、主题、依赖注入等)
这篇文章就以 真实项目结构 为例,完整讲一下:
- 如何在 AppDelegate 中自定义第一个展示页面
- 为什么会出现 SceneDelegate
- 如何在包含 SceneDelegate 的项目中设置首页
- 如果你不需要 SceneDelegate ------ 如何"完全移除"它,让项目更简单
二. 移除原根视图控制器
通常来讲ViewController很难满足项目需求,那么第一件事儿应该就是想办法删除这个原来的根视图控制器。
我们可以在左侧文件中找到Main.storyboard,会发现有一个小箭头指向这个ViewController,我们使用鼠标选中小箭头按删除键,即可移除原来的根视图。

三. AppDelegate
其实在iOS13之前,所有的事情都是在AppDelegate中完成的,AppDelegate的代码如下:
Swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
...
}
这是最简单也最容易理解的结构:AppDelegate是整个App的入口,我们可以在这里自己手动创建window,自定义设置根视图控制。
例如一个实际项目的AppDelegate代码如下:
Swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setupMain()
return true
}
/// 设置 main
private func setupMain() {
window = UIWindow(frame: UIScreen.main.bounds)
let tabBarController = GZTabBarController()
let nav = UINavigationController(rootViewController: tabBarController)
nav.setNavigationBarHidden(true, animated: false)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
}
这种写法极为常见:
- 手动创建 UIWindow
- 手动设置 rootViewController
- 你可以根据业务决定展示 TabBar / 登录页 / 引导页等任意界面
四. SceneDelegate
iOS 13之后多了一个 SceneDelegate,主要是因为iOS 13发布了多窗口(Multiple Scenes)机制,也就是在 iPad 上可以让一个 App 打开多个窗口。
因此苹果新增了 SceneDelegate,用于管理每一个窗口:
Swift
application → 多个 scene → 每个 scene 对应一个 UIWindow
所以新工程里你会看到:
- AppDelegate 不再负责 window
- SceneDelegate 接管 window 与 rootViewController
4.1 如果你的项目包含 SceneDelegate:那该怎么自定义首页?
就目前来我们能用xcode版本创建的项目必然包含SceneDelegate,但是也不需要慌,只需要把 AppDelegate 的 setupMain() 移到 SceneDelegate:
Swift
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
let tabBar = GZTabBarController()
let nav = UINavigationController(rootViewController: tabBar)
nav.setNavigationBarHidden(true, animated: false)
window?.rootViewController = nav
window?.makeKeyAndVisible()
}
但是 如果你的项目需要再iOS 13以下的系统中运行,那么AppDelegate中的代码也不能省略。
4.2 但是很多 iPhone App 根本不需要 SceneDelegate ------ 可以删除吗?可以!
绝大部分 iPhone 应用并不会用到 iPad 的多窗口,所以 SceneDelegate 完全是累赘。如果你希望项目保持最简单的 AppDelegate + Window 模式,只需要:
删除 SceneDelegate.swift 文件
直接从项目中删掉。
删除 Info.plist 里的 Scene 配置
Application Scene Manifest → UIApplicationSceneManifest
删掉后 App 就不会再尝试创建 Scene 了。
让 AppDelegate 恢复为 window 模式
然后就是把AppDelegate还当做APP入口,在 didFinishLaunchingWithOptions 中设置 rootViewController。
此时项目就回到最简单、最稳定的结构:AppDelegate 100% 负责。
五. 结语
无论使用哪种结构,都记住一句话:
第一个界面 = window.rootViewController,window 的创建者是谁 → 首页入口就写在哪里。
| 项目结构 | 首页入口位置 |
|---|---|
| 不含 SceneDelegate | AppDelegate 的 didFinishLaunchingWithOptions |
| 含 SceneDelegate | SceneDelegate 的 scene(_:willConnectTo:) |
| 删除 SceneDelegate | 又回到 AppDelegate |
在真实项目中,我们一般会:
- 使用 UINavigationController 包裹 TabBar
- 隐藏导航栏,由业务统一管理导航样式
- 为后续引导页 / 登录页 / 路由切换留出空间
这也是大量上线应用的通用写法。