MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的User Activity与Handoff协作

2023-05-023.0k 阅读

一、理解 User Activity

1.1 User Activity 基础概念

在 iOS 开发中,User Activity(用户活动)是一种用于跟踪用户在应用内执行的特定任务或操作的机制。它允许开发者定义用户正在做的事情,并将这些信息与系统进行交互。这不仅有助于应用在不同设备间提供无缝的体验,还对搜索和多任务处理等方面有积极影响。

从本质上讲,User Activity 是一个表示用户当前操作的对象。通过设置相关属性,如活动类型、标题、描述等,我们可以详细描述用户正在进行的活动。例如,用户正在阅读一篇文章,我们可以创建一个 User Activity,将活动类型设置为类似于“com.example.readingArticle”,标题为文章标题,描述为文章摘要等。

1.2 User Activity 的作用

  1. 多设备无缝切换(Handoff):这是 User Activity 最为重要的应用场景之一。当用户在一台设备(如 iPhone)上开始一项任务,然后切换到另一台设备(如 iPad)时,User Activity 可以让系统识别到用户在不同设备上的同一活动,从而实现无缝切换。例如,用户在 iPhone 上开始撰写邮件,然后在 iPad 上继续撰写,系统能够感知到这是同一个邮件撰写活动。
  2. Spotlight 搜索:User Activity 中的信息可以被 Spotlight 索引。这意味着用户可以通过 Spotlight 搜索来找到他们在应用内执行过的特定活动。例如,如果用户在应用内保存了一份重要文档,通过合适设置 User Activity,用户可以在 Spotlight 中搜索文档相关信息,快速打开该文档所在的活动界面。
  3. 近期使用应用列表:系统会根据 User Activity 来更新近期使用应用列表。当用户在应用内执行了某个活动后,该活动的相关信息可能会显示在近期使用应用列表中,方便用户快速回到该活动场景。

二、User Activity 在 Objective - C 中的实现

2.1 创建 User Activity

在 Objective - C 中,创建 User Activity 非常直观。首先,我们需要导入 CoreSpotlightUIKit 框架,因为 User Activity 相关类存在于这些框架中。

#import <CoreSpotlight/CoreSpotlight.h>
#import <UIKit/UIKit.h>

// 创建 User Activity
NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.someActivity"];
userActivity.title = @"My Activity Title";
userActivity.userInfo = @{@"key": @"value"};
userActivity.eligibleForSearch = YES;
userActivity.eligibleForHandoff = YES;

在上述代码中,我们使用 initWithActivityType: 方法初始化了一个 NSUserActivity 对象,并设置了其标题 title 和用户信息 userInfo。同时,我们通过设置 eligibleForSearcheligibleForHandoff 属性,分别表明该活动是否可用于搜索和 Handoff。

2.2 管理 User Activity 的生命周期

  1. 激活 User Activity:一旦创建了 User Activity,我们需要激活它,以便系统开始跟踪。这通常在视图控制器的 viewWillAppear: 方法中进行。
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.someActivity"];
    userActivity.title = @"My Activity Title";
    userActivity.userInfo = @{@"key": @"value"};
    userActivity.eligibleForSearch = YES;
    userActivity.eligibleForHandoff = YES;
    [self setUserActivity:userActivity];
    [userActivity becomeCurrent];
}

viewWillAppear: 方法中,我们创建并设置好 User Activity 后,通过 setUserActivity: 方法将其关联到当前视图控制器,并调用 becomeCurrent 方法激活它。

  1. 更新 User Activity:如果用户在活动过程中改变了相关信息,我们需要更新 User Activity。例如,用户在撰写文档过程中修改了标题,我们可以这样更新:
- (void)updateUserActivityWithNewTitle:(NSString *)newTitle {
    NSUserActivity *currentActivity = self.userActivity;
    if (currentActivity) {
        currentActivity.title = newTitle;
        [currentActivity updateUserActivity];
    }
}

在上述代码中,我们获取当前视图控制器关联的 User Activity,更新其标题后,调用 updateUserActivity 方法通知系统更新。

  1. 结束 User Activity:当用户完成活动或者离开相关界面时,我们需要结束 User Activity。这通常在视图控制器的 viewWillDisappear: 方法中进行。
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    NSUserActivity *userActivity = self.userActivity;
    if (userActivity) {
        [userActivity invalidate];
    }
}

通过调用 invalidate 方法,我们告知系统该 User Activity 已结束,系统将停止跟踪。

2.3 处理 User Activity 的恢复

当用户通过 Handoff 或者 Spotlight 搜索等方式重新启动一个 User Activity 时,应用需要能够正确恢复到相应状态。在应用的 AppDelegate 中,我们可以实现以下方法来处理 User Activity 的恢复:

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    NSString *activityType = userActivity.activityType;
    if ([activityType isEqualToString:@"com.example.someActivity"]) {
        // 根据 userActivity.userInfo 恢复应用状态
        NSDictionary *userInfo = userActivity.userInfo;
        // 例如,如果 userInfo 中有文章 ID,根据文章 ID 加载文章内容
        NSString *articleID = userInfo[@"articleID"];
        // 加载文章并显示相关视图
        // ...
        return YES;
    }
    return NO;
}

continueUserActivity:restorationHandler: 方法中,我们首先判断活动类型,然后根据 userInfo 中的信息来恢复应用到相应状态。

三、深入理解 Handoff

3.1 Handoff 原理

Handoff 依赖于多个系统服务协同工作。首先,设备之间需要通过 iCloud 进行配对和同步 User Activity 信息。当用户在一台设备上激活一个符合 Handoff 条件(即 eligibleForHandoffYES)的 User Activity 时,该活动信息会被发送到 iCloud。

然后,其他设备通过 iCloud 接收这些活动信息。设备会根据用户当前的使用场景和活动的相关性,决定是否在合适的时机向用户展示 Handoff 提示。例如,如果用户在 iPhone 上激活了一个文档编辑活动,当用户拿起 iPad 并解锁时,如果 iPad 检测到该活动与当前场景相关(比如用户经常在 iPad 上进行文档编辑),就会在锁定屏幕或者控制中心显示 Handoff 提示。

当用户点击 Handoff 提示时,接收设备会从 iCloud 下载完整的 User Activity 信息,并启动相应的应用,通过调用 continueUserActivity:restorationHandler: 方法来恢复应用状态。

3.2 Handoff 的条件

  1. 设备条件:参与 Handoff 的设备必须使用同一 iCloud 账户登录,并且设备之间需要打开蓝牙和 Wi - Fi 功能。这是因为 Handoff 依赖于蓝牙进行设备间的近距离通信,以及 Wi - Fi 来与 iCloud 同步 User Activity 信息。
  2. 应用条件:应用必须在 Info.plist 文件中配置支持 Handoff。具体来说,需要添加 NSUserActivityTypes 数组,并在其中指定应用支持的 User Activity 类型。例如:
<key>NSUserActivityTypes</key>
<array>
    <string>com.example.someActivity</string>
</array>

同时,应用需要按照前面所述正确实现 User Activity 的创建、激活、更新和恢复等功能,确保活动信息能够准确地在设备间传递和恢复。

3.3 优化 Handoff 体验

  1. 提供清晰的活动信息:在创建 User Activity 时,尽量提供详细且有意义的标题、描述和用户信息。这样在 Handoff 提示中,用户能够清楚地了解该活动的内容,从而更愿意点击进行切换。例如,对于一个地图导航活动,标题可以设置为“前往[目的地名称]的导航”,描述可以包含预计到达时间等信息。
  2. 及时更新活动状态:当用户在活动过程中对关键信息进行修改时,及时更新 User Activity。例如,在文档编辑活动中,用户修改了文档标题,应立即更新 User Activity 的标题,这样在 Handoff 到其他设备时,新设备上显示的文档标题也是最新的。
  3. 处理网络环境变化:由于 Handoff 依赖网络同步活动信息,应用需要妥善处理网络环境变化的情况。例如,当网络暂时不可用时,应用可以缓存 User Activity 的更新,待网络恢复后再同步到 iCloud,确保活动信息的一致性。

四、User Activity 与 Handoff 的高级应用

4.1 跨应用 Handoff

虽然 Handoff 通常在同一应用的不同设备间使用,但也可以实现跨应用的 Handoff。这需要多个应用之间进行协作,并且在 Info.plist 文件中配置相关的关联信息。

例如,假设应用 A 正在处理一个文档编辑活动,应用 B 是一个文档管理应用。应用 A 可以在创建 User Activity 时,通过设置特定的用户信息来表明该活动与应用 B 相关。同时,应用 B 需要在 Info.plist 文件中声明支持接收来自应用 A 的特定类型 User Activity。

<key>NSUserActivityTypes</key>
<array>
    <string>com.example.appA.documentEditActivity</string>
</array>

在应用 A 中,创建 User Activity 时:

NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.appA.documentEditActivity"];
userActivity.title = @"Editing Document";
userActivity.userInfo = @{@"documentID": @"12345", @"relatedApp": @"com.example.appB"};
userActivity.eligibleForHandoff = YES;

当用户在应用 A 中激活该活动并切换到安装了应用 B 的设备时,设备会检测到应用 B 支持接收该类型活动,并显示 Handoff 提示。应用 B 在 continueUserActivity:restorationHandler: 方法中,根据 userInfo 中的信息,如 documentID,来获取并展示相关文档。

4.2 结合 Core Data 使用 User Activity

如果应用使用 Core Data 来管理数据,我们可以将 Core Data 对象与 User Activity 紧密结合。例如,假设我们有一个基于 Core Data 的笔记应用,用户在编辑一篇笔记时,我们可以创建一个 User Activity,并将笔记的 NSManagedObjectID 存储在 User Activity 的 userInfo 中。

// 获取当前笔记的 NSManagedObjectID
NSManagedObjectID *noteObjectID = self.currentNote.objectID;
NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.noteEditing"];
userActivity.title = self.currentNote.title;
userActivity.userInfo = @{@"noteObjectID": [noteObjectID URIRepresentation]};
userActivity.eligibleForHandoff = YES;

当用户通过 Handoff 切换到其他设备时,在 continueUserActivity:restorationHandler: 方法中,我们可以根据 userInfo 中的 noteObjectID 重新获取笔记对象,并恢复到编辑状态。

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqualToString:@"com.example.noteEditing"]) {
        NSURL *noteObjectIDURL = userActivity.userInfo[@"noteObjectID"];
        NSManagedObjectID *noteObjectID = [self.persistentContainer.persistentStoreCoordinator managedObjectIDForURIRepresentation:noteObjectIDURL];
        NSManagedObject *note = [self.persistentContainer.viewContext objectWithID:noteObjectID];
        // 恢复笔记编辑界面
        //...
        return YES;
    }
    return NO;
}

通过这种方式,我们可以利用 Core Data 的强大功能,更有效地管理和恢复 User Activity 相关的数据。

4.3 处理复杂的 User Activity 状态

在一些复杂的应用场景中,User Activity 可能具有多种状态。例如,在一个项目管理应用中,用户可能处于项目创建、任务分配、进度跟踪等不同阶段的活动。

我们可以通过在 User Activity 的 userInfo 中添加状态相关的信息来管理这些复杂状态。例如:

NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.projectManagement"];
userActivity.title = @"Project Management Activity";
userActivity.userInfo = @{@"projectID": @"123", @"activityState": @"taskAssignment"};
userActivity.eligibleForHandoff = YES;

在恢复 User Activity 时,根据 activityState 来恢复到相应的具体状态。

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqualToString:@"com.example.projectManagement"]) {
        NSString *activityState = userActivity.userInfo[@"activityState"];
        if ([activityState isEqualToString:@"taskAssignment"]) {
            // 恢复到任务分配界面
            //...
        } else if ([activityState isEqualToString:@"progressTracking"]) {
            // 恢复到进度跟踪界面
            //...
        }
        return YES;
    }
    return NO;
}

这样,即使在复杂的应用逻辑下,也能通过 User Activity 实现准确的状态恢复和 Handoff 功能。

五、常见问题及解决方法

5.1 Handoff 提示不显示

  1. 检查设备设置:首先确保参与 Handoff 的设备都使用同一 iCloud 账户登录,并且蓝牙和 Wi - Fi 功能已打开。有时用户可能不小心关闭了蓝牙,导致 Handoff 提示无法显示。
  2. 确认应用配置:检查应用的 Info.plist 文件,确保 NSUserActivityTypes 数组中正确配置了支持的 User Activity 类型。同时,在代码中确认 User Activity 的 eligibleForHandoff 属性设置为 YES
  3. 网络问题:如果网络不稳定,可能会影响 User Activity 信息的同步,进而导致 Handoff 提示不显示。可以尝试在不同网络环境下测试,或者检查设备的网络连接状态。

5.2 User Activity 恢复失败

  1. 检查活动类型匹配:在 continueUserActivity:restorationHandler: 方法中,仔细检查活动类型是否正确匹配。如果活动类型不匹配,应用可能无法正确恢复。
  2. 数据完整性:确保在 User Activity 的 userInfo 中存储了恢复应用状态所需的完整数据。如果 userInfo 中的关键信息缺失,恢复过程可能失败。例如,在文档编辑活动中,如果 userInfo 中没有存储文档 ID,应用将无法加载正确的文档。
  3. 版本兼容性:如果应用进行了版本更新,可能存在旧版本 User Activity 与新版本应用不兼容的情况。在版本更新时,需要考虑如何处理旧版本的 User Activity,例如提供数据迁移机制或者对旧活动类型进行兼容处理。

5.3 Spotlight 搜索不生效

  1. 配置索引权限:确保应用在 Info.plist 文件中配置了正确的索引权限。例如,添加 NSAppTransportSecurity 字典,并设置 NSAllowsArbitraryLoadsYES,以允许应用与 Core Spotlight 进行数据交互。
  2. 活动设置:检查 User Activity 的 eligibleForSearch 属性是否设置为 YES。同时,提供足够有意义的标题、描述和用户信息,以便 Spotlight 能够准确索引。如果标题过于简单或者描述不清晰,可能导致搜索结果不准确或不显示。
  3. 数据更新频率:如果 User Activity 中的数据更新不及时,可能影响 Spotlight 搜索结果。确保在数据发生变化时,及时更新 User Activity,并调用 updateUserActivity 方法通知系统。

通过对这些常见问题的排查和解决,可以确保 User Activity 与 Handoff 功能在应用中稳定、可靠地运行。

通过深入理解和正确实现 User Activity 与 Handoff 协作,开发者能够为用户提供更加无缝、便捷的跨设备使用体验,提升应用的竞争力和用户满意度。无论是简单的文档编辑应用,还是复杂的项目管理应用,合理利用这些技术都能带来显著的优势。同时,随着 iOS 系统的不断更新,开发者也需要持续关注相关技术的变化,以不断优化应用的功能和体验。