iOS 13+ SceneDelegate 使用详解

在 iOS 13 及以上版本中,Apple 引入了 SceneDelegate (场景代理),核心目标是支持 多窗口功能 (iPadOS 多任务、iOS 分屏等),并将原 AppDelegate 中"UI 管理"和"场景生命周期"相关的职责拆分出来,实现更精细化的窗口控制。

一、SceneDelegate 的核心定位

SceneDelegateAppDelegate分工协作 的关系,二者职责边界清晰:

职责类别 AppDelegate(应用级) SceneDelegate(场景级)
生命周期管理 应用全局生命周期(启动、终止、内存警告) 单个场景生命周期(显示、隐藏、激活、后台)
UI 管理 不再负责具体窗口,仅处理全局配置 管理对应场景的 UIWindow、根视图控制器
系统事件 全局事件(推送授权、后台任务全局配置) 场景相关事件(深链接、窗口尺寸变化、状态保存)
多窗口支持 不直接参与,仅通过 UISceneSession 管理场景 每个窗口对应一个 SceneDelegate 实例

二、Scene 相关基础概念

在使用 SceneDelegate 前,需先理解以下核心概念:

  • Scene(场景) :对应一个独立的窗口或界面实例,例如 iPad 分屏时的两个应用窗口,每个窗口就是一个 Scene
  • UISceneSession:场景的"配置与状态管理者",记录场景的唯一标识、配置信息、状态(激活/后台),由系统管理。
  • Info.plist 配置 :iOS 13+ 应用需在 Info.plist 中声明 UISceneConfiguration,告诉系统如何创建和配置场景。

三、基础配置:开启 Scene 支持

若新建 iOS 13+ 项目,Xcode 会自动生成 SceneDelegate 文件和相关配置;若为旧项目适配,需手动完成以下步骤:

1. 添加 SceneDelegate 文件

  • 新建 SceneDelegate.hSceneDelegate.m(Objective-C)或 SceneDelegate.swift(Swift),默认实现如下:

Objective-C 版本

SceneDelegate.h

objc 复制代码
#import <UIKit/UIKit.h>

@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>

@property (strong, nonatomic) UIWindow * window;

@end

SceneDelegate.m

objc 复制代码
#import "SceneDelegate.h"

@implementation SceneDelegate

#pragma mark - UISceneSession Lifecycle
// 场景初始化(创建窗口)
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    // 确保场景是窗口场景(排除其他类型场景)
    if ([scene isKindOfClass:[UIWindowScene class]]) {
        UIWindowScene *windowScene = (UIWindowScene *)scene;
        // 创建窗口并绑定到当前场景
        self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
        self.window.frame = windowScene.coordinateSpace.bounds;
        // 设置根视图控制器
        self.window.rootViewController = [[UIViewController alloc] init];
        // 显示窗口
        [self.window makeKeyAndVisible];
    }
}

@end

Swift 版本

SceneDelegate.swift

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?.frame = windowScene.coordinateSpace.bounds
        // 设置根视图控制器
        window?.rootViewController = UIViewController()
        // 显示窗口
        window?.makeKeyAndVisible()
    }
}

2. 配置 Info.plist

需在 Info.plist 中添加 UISceneConfigurations 键,声明场景的配置信息(系统根据此创建场景):

  1. Info.plist 中右键 → Add Row ,输入 UISceneConfigurations(类型为 Dictionary)。
  2. UISceneConfigurations 下添加 UIWindowSceneSessionRoleApplication(类型为 Array),表示"应用主场景"。
  3. 在 Array 中添加一个 Dictionary,设置以下键:
    • UISceneConfigurationName:配置名称(如 Default Configuration,自定义)。
    • UISceneDelegateClassNameSceneDelegate 类名(如 $(PRODUCT_MODULE_NAME).SceneDelegate)。
    • UISceneStoryboardFile(可选):若用 Storyboard,指定主 Storyboard 名称。

配置效果(plist 源码示例)

xml 复制代码
<key>UISceneConfigurations</key>
<dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
        <dict>
            <key>UISceneConfigurationName</key>
            <string>Default Configuration</string>
            <key>UISceneDelegateClassName</key>
            <string>YourAppName.SceneDelegate</string>
            <key>UISceneStoryboardFile</key>
            <string>Main</string>
        </dict>
    </array>
</dict>

3. 修改 AppDelegate

iOS 13+ 中,AppDelegate 不再管理窗口,需删除原 window 属性和 UI 初始化代码,并添加场景会话相关方法:

Objective-C 版本(AppDelegate.m)

objc 复制代码
#import "AppDelegate.h"

@implementation AppDelegate

// 应用启动完成(仅处理全局初始化,不涉及UI)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 全局配置:注册推送、初始化第三方SDK等(不创建window)
    return YES;
}

// 系统请求创建新场景时调用(如用户开启多窗口)
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSession options:(UISceneConnectionOptions *)options {
    // 返回 Info.plist 中配置的场景配置(匹配名称)
    return [UISceneConfiguration configurationWithName:@"Default Configuration" sessionRole:connectingSession.role];
}

// 场景会话断开时调用(如用户关闭窗口)
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
    // 清理场景相关资源(如释放场景专属数据)
}

@end

Swift 版本(AppDelegate.swift)

swift 复制代码
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    // 移除原 window 属性!

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 全局初始化:注册推送、SDK等
        return true
    }

    // 配置场景连接
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    // 场景会话断开
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // 清理资源
    }
}

四、SceneDelegate 核心方法(场景生命周期)

SceneDelegate 的核心是 场景生命周期方法 ,对应单个窗口从"创建"到"销毁"的全过程,与原 AppDelegate 的 UI 生命周期方法类似,但粒度更细(每个场景独立触发)。

1. 场景初始化(连接会话)

objc 复制代码
// Objective-C
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    // 1. 校验场景类型(必须是 UIWindowScene)
    guard [scene isKindOfClass:[UIWindowScene class]] else { return; }
    UIWindowScene *windowScene = (UIWindowScene *)scene;
    
    // 2. 创建窗口并绑定到当前场景(关键:不再用 UIScreen.mainScreen)
    self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
    self.window.frame = windowScene.coordinateSpace.bounds; // 用场景的坐标空间
    
    // 3. 设置根视图控制器(示例:导航控制器包裹首页)
    UIViewController *homeVC = [[UIViewController alloc] init];
    homeVC.view.backgroundColor = UIColor.whiteColor;
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:homeVC];
    self.window.rootViewController = nav;
    
    // 4. 显示窗口
    [self.window makeKeyAndVisible];
    
    // 5. 处理启动参数(如通过深链接、推送启动场景)
    if (connectionOptions.urlContexts.count > 0) {
        NSURLContext *urlContext = connectionOptions.urlContexts.anyObject;
        [self handleDeepLink:urlContext.URL]; // 自定义深链接处理
    }
}

关键注意

iOS 13+ 中,窗口必须通过 UIWindowScene 初始化(initWithWindowScene:),不能再用 initWithFrame:UIScreen.mainScreen.bounds,否则多窗口场景下会出现 UI 错乱。

2. 场景激活/失活

objc 复制代码
// 场景即将激活(如窗口从后台切换到前台,或新窗口被打开)
- (void)sceneDidBecomeActive:(UIScene *)scene {
    // 恢复操作:重启定时器、刷新数据、恢复用户交互
}

// 场景即将失活(如窗口被切换到后台,或被其他窗口覆盖)
- (void)sceneWillResignActive:(UIScene *)scene {
    // 暂停操作:暂停定时器、保存临时数据、停止动画
}

3. 场景进入/退出后台

objc 复制代码
// 场景即将进入后台(如用户按 Home 键,或窗口被最小化)
- (void)sceneWillEnterForeground:(UIScene *)scene {
    // 前台恢复:刷新 UI 状态、重新加载网络数据
}

// 场景已进入后台(如窗口完全被隐藏)
- (void)sceneDidEnterBackground:(UIScene *)scene {
    // 后台处理:保存持久化数据、释放场景专属内存(如大图缓存)
}

4. 场景断开(销毁)

objc 复制代码
// 场景即将断开会话(如用户关闭窗口,或系统回收资源)
- (void)sceneDidDisconnect:(UIScene *)scene {
    // 清理操作:释放场景专属资源(如注销监听、关闭网络连接)
    // 注意:此时窗口已不可见,无需处理 UI
}

五、SceneDelegate 常见场景处理

1. 处理深链接(URL Scheme/Universal Link)

iOS 13+ 中,深链接不再通过 AppDelegateapplication:openURL:options: 处理,而是由 SceneDelegate 的以下方法接收:

objc 复制代码
// 处理通过 URL 打开场景的请求
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIURLContext *> *)URLContexts {
    for (UIURLContext *context in URLContexts) {
        NSURL *url = context.URL;
        // 解析 URL 并跳转页面(如 url = "yourapp://detail?id=123")
        if ([url.scheme isEqualToString:@"yourapp"] && [url.host isEqualToString:@"detail"]) {
            NSString *id = [url.query componentsSeparatedByString:@"="].lastObject;
            // 跳转到详情页(需获取当前场景的根视图控制器)
            UINavigationController *nav = (UINavigationController *)self.window.rootViewController;
            DetailViewController *detailVC = [[DetailViewController alloc] init];
            detailVC.detailId = id;
            [nav pushViewController:detailVC animated:YES];
        }
    }
}

2. 适配多窗口(获取当前场景)

在多窗口场景下,需通过 UISceneUIWindowScene 获取当前活跃的场景,避免操作错误窗口:

objc 复制代码
// 方式1:从视图控制器获取当前场景
UIViewController *currentVC = self;
UIWindowScene *currentScene = currentVC.view.window.windowScene;

// 方式2:获取所有活跃的场景
NSArray<UIWindowScene *> *activeScenes = [UIApplication.sharedApplication.connectedScenes filteredArrayUsingPredicate:
    [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
        return evaluatedObject.sceneState == UISceneStateActive;
    }]
].firstObject;

// 方式3:通过 SceneSession 获取场景
UISceneSession *currentSession = self.window.windowScene.session;
NSString *sceneId = currentSession.persistentIdentifier; // 场景唯一标识

3. 场景状态保存与恢复

若需在场景销毁后恢复状态(如用户重新打开窗口时恢复之前的页面),可通过 UISceneSessionstateRestorationActivity 实现:

objc 复制代码
// 1. 场景进入后台时保存状态
- (void)sceneDidEnterBackground:(UIScene *)scene {
    // 创建保存数据的 Activity
    NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:@"com.yourapp.scene.restore"];
    activity.userInfo = @{@"currentPage": @"detail", @"detailId": @"123"}; // 保存当前页面和参数
    // 关联到场景会话
    scene.session.stateRestorationActivity = activity;
}

// 2. 场景初始化时恢复状态
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    // 检查是否有恢复的 Activity
    if (session.stateRestorationActivity) {
        NSDictionary *userInfo = session.stateRestorationActivity.userInfo;
        NSString *currentPage = userInfo[@"currentPage"];
        if ([currentPage isEqualToString:@"detail"]) {
            // 恢复到详情页
            NSString *detailId = userInfo[@"detailId"];
            // ... 跳转逻辑 ...
        }
    }
}

六、兼容 iOS 12 及以下版本

若应用需支持 iOS 12 及以下,需通过 版本判断 区分 AppDelegateSceneDelegate 的职责:

1. AppDelegate 中兼容 UI 初始化

objc 复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 全局初始化...
    
    // iOS 12 及以下:在 AppDelegate 中创建窗口
    if (@available(iOS 13.0, *)) {
        // iOS 13+:由 SceneDelegate 处理 UI,此处不做操作
    } else {
        self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
        self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[UIViewController alloc] init]];
        [self.window makeKeyAndVisible];
    }
    
    return YES;
}

2. 深链接兼容处理

objc 复制代码
// AppDelegate 中(iOS 12 及以下处理深链接)
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    if (@available(iOS 13.0, *)) {
        return NO; // iOS 13+ 由 SceneDelegate 处理
    } else {
        // iOS 12 及以下处理深链接...
        return YES;
    }
}

// SceneDelegate 中(iOS 13+ 处理深链接)
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIURLContext *> *)URLContexts {
    // ... 处理逻辑 ...
}

七、常见问题与注意事项

  1. 窗口初始化错误 :iOS 13+ 必须用 initWithWindowScene: 创建窗口,否则多窗口时 UI 会错乱。
  2. 多场景资源冲突 :每个场景的 SceneDelegate 是独立实例,需避免共享资源(如单例数据)在多场景下的状态不一致。
  3. SceneSession 唯一标识UISceneSession.persistentIdentifier 是场景的唯一标识,可用于区分不同窗口的状态
相关推荐
007php00712 小时前
Go 错误处理:用 panic 取代 err != nil 的模式
java·linux·服务器·后端·ios·golang·xcode
YungFan1 天前
iOS26适配指南之Liquid Glass App Icon
ios·swift
忧了个桑2 天前
从Demo到生产:VIPER架构的生产级模块化方案
ios·架构
如此风景2 天前
AppDelegate 详解
ios
如此风景2 天前
Swift基础学习文档
ios
如此风景2 天前
Object-C基础学习文档
ios
Digitally2 天前
如何将iPhone上的隐藏照片传输到电脑
ios·cocoa·iphone
2501_915918412 天前
uni-app 跨平台项目的 iOS 上架流程:多工具组合的高效协作方案
android·ios·小程序·https·uni-app·iphone·webview
Digitally2 天前
如何将iPhone日历传输到电脑
ios·iphone