iOS适配SceneDelegate

据说苹果要求,使用iOS 27对应的Xcode版本构建应用,必须适配UIScene,使用SceneDelegate进行创建窗口,否则无法启动。那么可能需要提前适配。

特别注意SceneDelegate只支持iOS 13以上系统,如果存在要兼容iOS13以下系统,需要适配SceneDelegate的同时,保留AppDelegate的方式。如无特别需要,最好还是修改最低支持版本为最好。

首先说下AppDelegateSceneDelegate的区别,以及适合哪些场景,这样才方便迁移。

AppDelegate:管理整个应用程序的生命周期;一个应用只有一个AppDelegate实例,从启动到终止,全程存在;
主要职责:
1、应用生命周期:响应应用启动、进入后台、被终止等关键事件。
2、核心系统回调:处理推送通知注册、远程控制、后台任务等。
3、初始化共享资源:配置数据库、网络框架、第三方SDK等全局组件。
4、在支持多场景时:负责创建和管理SceneDelegate 的配置;

SceneDelegate:管理单个窗口场景的生命周期。一个应用可以存在多个。
主要职责:
1、窗口界面管理:负责创建、显示和管理 UIWindow及根视图控制器。
2、场景生命周期:响应场景连接、激活、失活、进入后台、断开连接等。
3、用户界面和状态恢复:管理特定窗口的用户界面状态。
4、处理多任务:在iPadOS上,支持分屏、滑动悬浮等。

适配点:
1、创建SceneDelegate文件(附件有已创建的文件,可直接使用)
2、修改info.plist文件
直接Source Code添加以下代码

<key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
        <key>UISceneConfigurations</key>
        <dict>
            <key>UIWindowSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneConfigurationName</key>
                    <string>Default Configuration</string>
                    <key>UISceneDelegateClassName</key>
                    <string>SceneDelegate</string>
                    <key>UISceneStoryboardFile</key>
                    <string>Main</string>
                </dict>
            </array>
        </dict>
</dict>

备注:
a、一般应用都为单窗口模式,UIApplicationSupportsMultipleScenes
要配置为NO,如果确实是多窗口模式,切记修改为YES
b、如果项目中没有使用Main.Storyboard文件,则去掉

<key>UISceneStoryboardFile</key>
    <string>Main</string>

3、在appdelegate.m中新增以下两个方法

#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options  API_AVAILABLE(ios(13.0)){
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}


- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions  API_AVAILABLE(ios(13.0)){
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}

备注:Default Configuration的名称必须与第2点info.plistUISceneConfigurationName的值一致。

4、AppDelegate中的applicationWillEnterForeground
applicationDidBecomeActiveapplicationWillResignActiveapplicationDidEnterBackground
方法将不会被调用,但是该事件的通知还是会被触发。

通知依旧会被触发
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];

可以将这其中的逻辑移动到sceneWillEnterForegroundsceneDidBecomeActivesceneWillResignActivesceneDidEnterBackground中,或者使用通知监听以上事件来执行方法;

5、[UIApplication sharedApplication].delegate.window已经获取不到window,且该属性已被屏蔽。单窗口模式由于只会生成一份SceneDelegate,其中的windowwindowSceneAPP生命周期中也只会存在一份,可以取巧实现一个单例,保存windowwindowScene。以前使用[UIApplication sharedApplication].delegate.window或者[UIApplication sharedApplication].keyWindow的地方,直接使用单例保存的window

6、在SceneDelegate的方法

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions  API_AVAILABLE(ios(13.0));

中初始化window,创建window需要使用initWithWindowScene的方式,其中windowScene为方法中的scene,需要转换一下:

UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];

这里创建的window在单窗口模式下,等同于AppDelegate模式中的[UIApplication sharedApplication].delegate.window。直接保存在单例中,后续使用即可。

tip:在调试过程中发现,在willConnectToSession中设置window上的广告页,即启动页-->加载在window上的广告页,偶尔会出现启动页到广告页闪一下的情况,大概率猜测是window还未创建完成,还未呈现,但又向window上添加UI导致的, 这里增加一个0.25秒的延迟即可解决,让window实际呈现后再去创建广告页。

///创建window
    UIWindowScene *windowScene = (UIWindowScene *)scene;
    self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
    self.window.backgroundColor = UIColor.white_xFFFFFF;
    self.window.rootViewController = [[PNCLaunchDefaultRootVC alloc] init];
    [self.window makeKeyAndVisible];
    [PNCWindowSceneManager manager].keyWindow = self.window;
    [PNCWindowSceneManager manager].keyWindowScene = windowScene;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self lazyInitialization];
    });

7、如果存在启动广告页是通过window呈现的,初始化广告页的window时,需要使用initWithWindowScene方式初始化,单窗口直接使用单例中保存windowScene来创建即可,多窗口模式未测试过。

8、Scheme热启动适配
适配SceneDelegate后,AppDelegate中的方法:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;

将不会被调用(网上有说通过Scheme唤起,APP如果是冷启动,该方法会被调用,但是实际调试发现,该方法始终未被调用),需要将代码逻辑迁移至SceneDelegate中:

/// 该方法只会在willConnectToSession方法执行完成后被调用,即必须有窗口成为活跃窗口才会被触发。所以通过Scheme冷启动唤起APP时,该方法不会被触发,需要按照第10点的来适配冷启动。
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts;

有区别的地方在于SceneDelegate中的URLContexts是一个元组,对应多窗口和可能存在的多次点击重复唤起场景。如果是单窗口应用,极低的概率URLContexts中存在多个值。 所以单窗口应用中,其实可以将元组当成只包含一个数据来处理。URLContexts转换为URL的方式为:

UIOpenURLContext *context = URLContexts.anyObject;
if (!context) {
  return;
}
NSURL *url = context.URL;

9、Universal Link热启动适配
适配SceneDelegate后,AppDelegate中的方法:

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler;

将不会被调用(网上有说通过Universal Link唤起,APP如果是冷启动,该方法会被调用,但是实际调试发现,该方法始终未被调用),需要将代码逻辑迁移至SceneDelegate中,同时AppDelegate中的方法不删除,其他业务逻辑可删,直接return YES

/// 该方法只会在willConnectToSession方法执行完成后被调用,即必须有窗口成为活跃窗口才会被触发。所以通过Universal Link冷启动唤起APP时,该方法不会被触发,需要按照第10点的来适配冷启动。
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity;

对于这两个方法,AppDelegateSceneDelegate都是一样的,不需要特别注意,如果是多窗口模式,需要注意每个SceneDelegate的方法都会被触发。

10、SchemeUniversal Link冷启动适配
上述中有提到,SceneDelegate中的方法均在热启动的时候才会被触发,但是AppDelegate中的方法冷启动又无法被触发,这里需要单独对冷启动适配;
需要再SceneDelegate的方法中进行处理,同时AppDelegate中的方法不删除,其他业务逻辑可删,直接return YES

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions;

该方法在窗口创建时会被调用,每个SceneDelegate中的这个方法仅会被执行一次。方法中connectionOptions参数会携带SchemeUniversal Link中的数据。使用示例如下:

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions  API_AVAILABLE(ios(13.0)) {
    /// 处理Scheme
    if (connectionOptions.URLContexts.count > 0) {
        UIOpenURLContext *context = connectionOptions.URLContexts.anyObject;
        if (context.URL && [NSObject isNotEmptyString:context.URL.absoluteString]) {
            /// 在这里处理逻辑,但是需要考虑window可能仍然未被成功创建,或者有广告页等延时,可以将数据存储起来,在Home页在处理相应逻辑
        }
    }
    /// 处理Universal Link
    if (connectionOptions.userActivities.count > 0) {
        NSUserActivity *userActivity = connectionOptions.userActivities.anyObject;
        if (userActivity && userActivity.webpageURL && [NSObject isNotEmptyString:userActivity.webpageURL.absoluteString]) {
             /// 在这里处理逻辑,但是需要考虑window可能仍然未被成功创建,或者有广告页等延时,可以将数据存储起来,在Home页在处理相应逻辑
        }
    }
    /// 继续创建窗口
}

WindowSceneManager为自定义保存windowwindowScene的单例;
11、[UIApplication sharedApplication].statusBarOrientation属性已被废弃,搜索是否使用,有使用则替换成UIInterfaceOrientation orientation = [WindowSceneManager manager].keyWindowScene.interfaceOrientation;

12、[UIApplication sharedApplication].statusBarFrame属性已被废弃,搜索是否使用,有则替换成[WindowSceneManager manager].keyWindowScene.statusBarManager.statusBarFrame

13、[UIApplication sharedApplication].windows属性已被废弃,搜索是否使用,有则替换成[WindowSceneManager manager].keyWindowScene.windows
tip:要注意下UIApplication.sharedApplication.windowsUIApplication.sharedApplication.keyWindow,记得同步替换。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

友情链接更多精彩内容