UIWindowScene 使用指南:掌握 iOS 多窗口架构

引言

在 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 和需要复杂多任务处理的应用。通过合理使用场景管理,可以:

  1. 提供更好的多任务体验
  2. 支持外部显示器
  3. 实现高效的状态保存与恢复
  4. 优化内存使用

虽然学习曲线较陡,但掌握 UIWindowScene 将显著提升应用的现代化水平和用户体验。


示例项目 : 完整的示例代码可以在 GitHub 仓库 找到。

进一步阅读:

相关推荐
崽崽长肉肉7 小时前
swift中的知识总结(一)
ios·swift
2501_915106329 小时前
HTTP 协议详解,HTTP 协议在真实运行环境中的表现差异
网络·网络协议·http·ios·小程序·uni-app·iphone
柯南二号12 小时前
【大前端】【iOS】iOS 真实项目可落地目录结构方案
前端·ios
2501_9160074712 小时前
iOS与Android符号还原服务统一重构实践总结
android·ios·小程序·重构·uni-app·iphone·webview
二流小码农13 小时前
鸿蒙开发:自定义一个圆形动画菜单
android·ios·harmonyos
00后程序员张14 小时前
fastlane 结合 appuploader 命令行实现跨平台上传发布 iOS App
android·ios·小程序·https·uni-app·iphone·webview
2501_9151063214 小时前
iOS 性能优化这件事,结合多工具分析运行期性能问题
android·ios·性能优化·小程序·uni-app·cocoa·iphone
游戏开发爱好者814 小时前
App Store 上架流程,结合多工具协作
android·ios·小程序·https·uni-app·iphone·webview
2501_9159214316 小时前
uni-app 的 iOS 打包与上架流程,多工具协作
android·ios·小程序·uni-app·cocoa·iphone·webview