引言
在 iOS 13 之前,iOS 应用通常只有一个主窗口(UIWindow)。但随着 iPadOS 的推出和多任务处理需求的增加,Apple 引入了 UIWindowScene 架构,让单个应用可以同时管理多个窗口,每个窗口都有自己的场景(Scene)。本文将深入探讨 UIWindowScene 的核心概念和使用方法。
什么是 UIWindowScene?
UIWindowScene 是 iOS 13+ 中引入的新架构,它代表了应用程序用户界面的一个实例。每个场景都有自己的窗口、视图控制器层级和生命周期管理。
核心组件关系
objectivec
UISceneSession → UIWindowScene → UIWindow → UIViewController
↓
UISceneConfiguration
基础配置
1. 项目设置
首先需要在 Info.plist 中启用多场景支持:
xml
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
2. SceneDelegate 实现
swift
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = YourRootViewController()
window?.makeKeyAndVisible()
// 处理深度链接
if let userActivity = connectionOptions.userActivities.first {
self.scene(scene, continue: userActivity)
}
}
func sceneDidDisconnect(_ scene: UIScene) {
// 场景被系统释放时调用
}
func sceneDidBecomeActive(_ scene: UIScene) {
// 场景变为活动状态时调用
}
func sceneWillResignActive(_ scene: UIScene) {
// 场景即将变为非活动状态时调用
}
func sceneWillEnterForeground(_ scene: UIScene) {
// 场景即将进入前台
}
func sceneDidEnterBackground(_ scene: UIScene) {
// 场景进入后台
}
}
创建和管理多个场景
1. 动态创建新窗口
swift
class SceneManager {
static func createNewScene(with userInfo: [String: Any]? = nil) {
let activity = NSUserActivity(activityType: "com.yourapp.newWindow")
activity.userInfo = userInfo
activity.targetContentIdentifier = "newWindow"
let options = UIScene.ActivationRequestOptions()
options.requestingScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
UIApplication.shared.requestSceneSessionActivation(
nil,
userActivity: activity,
options: options,
errorHandler: { error in
print("Failed to create new scene: \(error)")
}
)
}
}
2. 场景配置管理
swift
// 自定义场景配置
class CustomSceneDelegate: UIResponder, UIWindowSceneDelegate {
static let configurationName = "CustomSceneConfiguration"
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
// 根据场景角色自定义配置
if session.role == .windowApplication {
configureApplicationWindow(scene: windowScene,
session: session,
options: connectionOptions)
} else if session.role == .windowExternalDisplay {
configureExternalDisplayWindow(scene: windowScene)
}
}
private func configureApplicationWindow(scene: UIWindowScene,
session: UISceneSession,
options: UIScene.ConnectionOptions) {
// 主窗口配置
let window = UIWindow(windowScene: scene)
// 根据用户活动恢复状态
if let userActivity = options.userActivities.first {
window.rootViewController = restoreViewController(from: userActivity)
} else {
window.rootViewController = UIViewController()
}
window.makeKeyAndVisible()
self.window = window
}
}
场景间通信与数据共享
1. 使用 UserActivity 传递数据
swift
class DocumentViewController: UIViewController {
var document: Document?
func openInNewWindow() {
guard let document = document else { return }
let userActivity = NSUserActivity(activityType: "com.yourapp.editDocument")
userActivity.title = "Editing \(document.title)"
userActivity.userInfo = ["documentId": document.id]
userActivity.targetContentIdentifier = document.id
let options = UIScene.ActivationRequestOptions()
UIApplication.shared.requestSceneSessionActivation(
nil,
userActivity: userActivity,
options: options,
errorHandler: nil
)
}
}
// 在 SceneDelegate 中处理
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard let windowScene = scene as? UIWindowScene,
let documentId = userActivity.userInfo?["documentId"] as? String else {
return
}
let document = fetchDocument(by: documentId)
let editorVC = DocumentEditorViewController(document: document)
windowScene.windows.first?.rootViewController = editorVC
}
2. 使用通知中心通信
swift
extension Notification.Name {
static let documentDidChange = Notification.Name("documentDidChange")
static let sceneDidBecomeActive = Notification.Name("sceneDidBecomeActive")
}
class DocumentManager {
static let shared = DocumentManager()
private init() {}
func updateDocument(_ document: Document) {
// 更新数据
NotificationCenter.default.post(
name: .documentDidChange,
object: nil,
userInfo: ["document": document]
)
}
}
高级功能
1. 外部显示器支持
swift
class ExternalDisplayManager {
static func setupExternalDisplay() {
// 监听外部显示器连接
NotificationCenter.default.addObserver(
self,
selector: #selector(handleScreenConnect),
name: UIScreen.didConnectNotification,
object: nil
)
}
@objc private static func handleScreenConnect(notification: Notification) {
guard let newScreen = notification.object as? UIScreen,
newScreen != UIScreen.main else { return }
let options = UIScene.ActivationRequestOptions()
options.requestingScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let activity = NSUserActivity(activityType: "externalDisplay")
UIApplication.shared.requestSceneSessionActivation(
nil,
userActivity: activity,
options: options,
errorHandler: nil
)
}
}
// 在 SceneDelegate 中配置外部显示器场景
func configureExternalDisplayWindow(scene: UIWindowScene) {
let window = UIWindow(windowScene: scene)
window.screen = UIScreen.screens.last // 使用外部显示器
window.rootViewController = ExternalDisplayViewController()
window.makeKeyAndVisible()
}
2. 场景状态保存与恢复
swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
// 返回用于恢复场景状态的 activity
let activity = NSUserActivity(activityType: "restoration")
if let rootVC = window?.rootViewController as? Restorable {
activity.addUserInfoEntries(from: rootVC.restorationInfo)
}
return activity
}
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// 检查是否有保存的状态
if let restorationActivity = session.stateRestorationActivity {
restoreState(from: restorationActivity)
}
}
}
最佳实践
1. 内存管理
swift
class MemoryAwareSceneDelegate: UIResponder, UIWindowSceneDelegate {
func sceneDidEnterBackground(_ scene: UIScene) {
// 释放不必要的资源
if let vc = window?.rootViewController as? MemoryManageable {
vc.releaseUnnecessaryResources()
}
}
func sceneWillEnterForeground(_ scene: UIScene) {
// 恢复必要的资源
if let vc = window?.rootViewController as? MemoryManageable {
vc.restoreResources()
}
}
}
2. 错误处理
swift
enum SceneError: Error {
case sceneCreationFailed
case invalidConfiguration
case resourceUnavailable
}
class RobustSceneManager {
static func createSceneSafely(configuration: UISceneConfiguration,
completion: @escaping (Result<UIWindowScene, SceneError>) -> Void) {
let options = UIScene.ActivationRequestOptions()
UIApplication.shared.requestSceneSessionActivation(
nil,
userActivity: nil,
options: options
) { error in
if let error = error {
completion(.failure(.sceneCreationFailed))
} else {
// 监控新场景创建
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let newScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.last {
completion(.success(newScene))
} else {
completion(.failure(.sceneCreationFailed))
}
}
}
}
}
}
调试技巧
1. 场景信息日志
swift
extension UIWindowScene {
func logSceneInfo() {
print("""
Scene Information:
- Session: \(session)
- Role: \(session.role)
- Windows: \(windows.count)
- Screen: \(screen)
- Activation State: \(activationState)
""")
}
}
// 在 AppDelegate 中监控所有场景
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
print("Connecting scene: \(connectingSceneSession)")
return UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)
}
2. 内存泄漏检测
swift
class SceneLeakDetector {
static var activeScenes: [String: WeakReference<UIWindowScene>] = [:]
static func trackScene(_ scene: UIWindowScene) {
let identifier = "\(ObjectIdentifier(scene).hashValue)"
activeScenes[identifier] = WeakReference(object: scene)
// 定期检查泄漏
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.checkForLeaks()
}
}
private static func checkForLeaks() {
activeScenes = activeScenes.filter { $0.value.object != nil }
print("Active scenes: \(activeScenes.count)")
}
}
class WeakReference<T: AnyObject> {
weak var object: T?
init(object: T) {
self.object = object
}
}
兼容性考虑
1. 向后兼容 iOS 12
swift
@available(iOS 13.0, *)
class ModernSceneDelegate: UIResponder, UIWindowSceneDelegate {
// iOS 13+ 实现
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13.0, *) {
// 使用场景架构
} else {
// 传统 UIWindow 设置
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UIViewController()
window?.makeKeyAndVisible()
}
return true
}
}
结语
UIWindowScene 架构为 iOS 应用带来了强大的多窗口支持,特别适合 iPadOS 和需要复杂多任务处理的应用。通过合理使用场景管理,可以:
- 提供更好的多任务体验
- 支持外部显示器
- 实现高效的状态保存与恢复
- 优化内存使用
虽然学习曲线较陡,但掌握 UIWindowScene 将显著提升应用的现代化水平和用户体验。
示例项目 : 完整的示例代码可以在 GitHub 仓库 找到。
进一步阅读: