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

Objective-C中的Handoff与Continuity功能开发

2022-03-243.4k 阅读

一、Handoff与Continuity概述

Handoff 和 Continuity 是苹果生态系统中非常强大的功能,它们允许用户在不同的苹果设备(如 iPhone、iPad、Mac 等)之间无缝切换任务和共享数据。Continuity 是一个宽泛的概念,涵盖了一系列增强设备间协同工作的功能,而 Handoff 是 Continuity 的一个重要组成部分,主要实现应用在不同设备间的无缝转移。

在 Objective - C 开发中,实现 Handoff 和 Continuity 功能能够极大提升用户体验,使应用在苹果设备生态中更加流畅地运行。例如,用户在 iPhone 上开始撰写邮件,然后可以在 Mac 上继续完成并发送,整个过程几乎不需要额外的操作,应用状态和数据能够平滑过渡。

二、Handoff功能开发

(一)配置项目以支持Handoff

  1. 启用 Handoff 功能: 在 Xcode 项目中,打开项目设置,选择对应的 target。在“Capabilities”选项卡中,找到“Continuity”并启用“Handoff”。这一步会自动为项目添加必要的 entitlements 文件,并配置相关权限。
  2. 设置应用组: Handoff 需要应用在不同设备间共享数据,所以要设置应用组。同样在“Capabilities”选项卡中,启用“App Groups”,并添加一个新的应用组,格式通常为group.com.yourcompany.yourapp。应用组用于不同设备上的应用实例之间共享数据容器。

(二)定义活动类型

  1. 创建活动类型: 在项目的Info.plist文件中,添加一个新的密钥NSUserActivityTypes,它是一个数组。在数组中添加自定义的活动类型,格式为com.yourcompany.yourapp.activitytype。例如,如果你的应用是一个笔记应用,活动类型可以是com.example.notesapp.editnote
  2. 注册活动类型: 在应用代码中,通常在AppDelegateapplication:didFinishLaunchingWithOptions:方法中注册活动类型。示例代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:@"com.example.notesapp.editnote"];
    activity.title = @"Edit Note";
    activity.keywords = @[@"note", @"edit"];
    // 这里可以设置更多的用户活动属性
    [self.window.rootViewController setUserActivity:activity];
    [activity becomeCurrent];
    return YES;
}

在上述代码中,创建了一个NSUserActivity实例,设置了活动类型、标题和关键字。然后将其关联到视图控制器,并使其成为当前活动。

(三)传递活动数据

  1. 在源设备上设置活动数据: 假设在一个笔记应用中,用户正在编辑一个笔记。当用户准备进行 Handoff 时,需要将当前笔记的相关数据设置到NSUserActivity中。例如:
- (void)prepareForHandoff {
    NSUserActivity *currentActivity = self.window.rootViewController.userActivity;
    if (currentActivity.activityType == @"com.example.notesapp.editnote") {
        Note *currentNote = self.currentlyEditingNote;
        [currentActivity setUserInfo:@{@"noteTitle": currentNote.title, @"noteContent": currentNote.content}];
    }
}

在这个方法中,获取当前的NSUserActivity,如果活动类型匹配,将当前笔记的标题和内容作为用户信息设置到活动中。

  1. 在目标设备上接收活动数据: 在目标设备(如 Mac)的应用中,需要监听 Handoff 活动的到来并获取数据。在AppDelegateapplication:continueUserActivity:restorationHandler:方法中处理,示例代码如下:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqualToString:@"com.example.notesapp.editnote"]) {
        NSDictionary *userInfo = userActivity.userInfo;
        NSString *noteTitle = userInfo[@"noteTitle"];
        NSString *noteContent = userInfo[@"noteContent"];
        // 根据获取的数据恢复笔记编辑状态
        [self restoreNoteWithTitle:noteTitle content:noteContent];
        return YES;
    }
    return NO;
}

此代码检查活动类型是否为预期的笔记编辑活动类型,如果是,则从用户信息中获取笔记标题和内容,并调用方法恢复笔记编辑状态。

(四)更新活动状态

  1. 活动进行中更新: 在应用运行过程中,如果活动状态发生变化,需要更新NSUserActivity。例如,在笔记应用中,用户修改了笔记内容,需要更新活动中的数据。
- (void)noteContentDidChange {
    NSUserActivity *currentActivity = self.window.rootViewController.userActivity;
    if (currentActivity.activityType == @"com.example.notesapp.editnote") {
        Note *currentNote = self.currentlyEditingNote;
        [currentActivity setUserInfo:@{@"noteTitle": currentNote.title, @"noteContent": currentNote.content}];
        [currentActivity updateUserActivity];
    }
}

在这个方法中,先检查活动类型,然后更新用户信息,并调用updateUserActivity方法通知系统活动状态已更改。

  1. 活动完成更新: 当活动完成时,如笔记编辑完成并保存,需要结束活动。
- (void)noteEditingFinished {
    NSUserActivity *currentActivity = self.window.rootViewController.userActivity;
    if (currentActivity.activityType == @"com.example.notesapp.editnote") {
        [currentActivity invalidate];
    }
}

这里调用invalidate方法使活动无效,系统将不再显示该活动的 Handoff 选项。

三、Continuity其他功能开发

(一)通用剪贴板

  1. 原理: 通用剪贴板允许用户在一个设备上复制文本、图像等内容,然后在另一个设备上粘贴。它基于 iCloud 进行数据同步,不同设备上的应用通过系统提供的 API 访问剪贴板数据。

  2. 使用方法: 在 Objective - C 中,复制数据到剪贴板非常简单。例如,复制一段文本:

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = @"This is a sample text to copy";

在目标设备上获取剪贴板数据:

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSString *copiedText = pasteboard.string;

对于图像等其他类型的数据,也可以类似处理。例如,复制图像:

UIImage *image = [UIImage imageNamed:@"sampleImage"];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.image = image;

获取图像:

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
UIImage *copiedImage = pasteboard.image;

(二)AirDrop

  1. 配置项目支持AirDrop: 在 Xcode 项目的Info.plist文件中,添加NSAirDropSharingEnabled密钥,并将其值设置为YES,以启用应用的 AirDrop 共享功能。

  2. 发送数据: 使用UIDocumentInteractionController来实现通过 AirDrop 发送文件。例如,发送一个 PDF 文件:

NSURL *fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"sample" ofType:@"pdf"]];
UIDocumentInteractionController *interactionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
interactionController.delegate = self;
[interactionController presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];

在上述代码中,首先获取要发送的 PDF 文件的 URL,然后创建UIDocumentInteractionController实例,并通过presentOpenInMenuFromRect:inView:animated:方法弹出共享菜单,其中包含 AirDrop 选项。

  1. 接收数据: 应用需要注册为能够接收特定类型文件的应用。在Info.plist文件中,添加CFBundleDocumentTypes数组,定义应用能够处理的文件类型。例如,接收 PDF 文件:
<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>PDF Document</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSHandlerRank</key>
        <string>Owner</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.adobe.pdf</string>
        </array>
    </dict>
</array>

当通过 AirDrop 接收到文件时,应用的AppDelegateapplication:openURL:options:方法会被调用,在该方法中处理接收到的文件。

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    if ([[url pathExtension] isEqualToString:@"pdf"]) {
        // 处理接收到的PDF文件
        [self handleReceivedPDF:url];
        return YES;
    }
    return NO;
}

(三)接力(Handoff相关扩展)

  1. 设置接力功能: 除了基本的 Handoff 配置,接力功能还需要确保设备间的 iCloud 同步正常工作。在应用代码中,可以进一步优化活动的优先级和显示。例如,设置活动的isEligibleForHandoff属性为YES,表示该活动可以用于接力。
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:@"com.example.notesapp.editnote"];
activity.title = @"Edit Note";
activity.keywords = @[@"note", @"edit"];
activity.isEligibleForHandoff = YES;
[self.window.rootViewController setUserActivity:activity];
[activity becomeCurrent];
  1. 跨设备状态同步: 接力功能要求应用在不同设备间保持状态同步。例如,在笔记应用中,可以使用 iCloud Key - Value Store 来同步笔记的编辑状态。在源设备上更新笔记状态:
NSUbiquitousKeyValueStore *keyValueStore = [NSUbiquitousKeyValueStore defaultStore];
[keyValueStore setObject:@"editing" forKey:@"noteEditingStatus"];
[keyValueStore synchronize];

在目标设备上获取并处理同步状态:

NSUbiquitousKeyValueStore *keyValueStore = [NSUbiquitousKeyValueStore defaultStore];
NSString *status = [keyValueStore objectForKey:@"noteEditingStatus"];
if ([status isEqualToString:@"editing"]) {
    // 恢复笔记编辑状态
    [self restoreEditingState];
}

四、常见问题与解决方法

(一)Handoff不显示

  1. 原因分析

    • 可能是应用没有正确配置 Handoff 权限,如未在 Xcode 中启用相关功能或 entitlements 文件配置错误。
    • 活动类型定义不正确,NSUserActivityTypesInfo.plist中设置有误,或者在代码中注册活动类型时出现问题。
    • 设备间的网络连接或 iCloud 同步异常,Handoff 需要设备在同一 Wi - Fi 网络下且 iCloud 功能正常。
  2. 解决方法

    • 仔细检查 Xcode 项目的“Capabilities”选项卡,确保“Handoff”和“App Groups”已正确启用,并且 entitlements 文件路径和内容正确。
    • 确认Info.plistNSUserActivityTypes的格式和值正确,在代码中注册活动类型时参数无误。
    • 检查设备的网络连接,确保设备连接到同一 Wi - Fi 网络,并且 iCloud 设置正确,iCloud 功能可用。

(二)通用剪贴板数据不同步

  1. 原因分析

    • iCloud 同步问题,如 iCloud 账户未登录或 iCloud 存储空间已满。
    • 应用在复制或粘贴数据时出现错误,例如,在复制图像时,图像格式可能不被目标设备支持。
    • 设备间的系统版本差异,某些较旧的系统版本可能对通用剪贴板功能支持不完善。
  2. 解决方法

    • 确保设备上已登录有效的 iCloud 账户,并且 iCloud 存储空间充足。可以在设备的设置中查看 iCloud 状态和存储空间使用情况。
    • 在复制数据时,确保数据格式通用。例如,对于图像,可以将其转换为常见的格式(如 PNG 或 JPEG)再进行复制。
    • 尽量使设备的系统版本保持一致,或者参考苹果官方文档,了解不同系统版本对通用剪贴板功能的支持情况,针对特定版本进行优化。

(三)AirDrop发送失败

  1. 原因分析

    • 设备的 AirDrop 设置问题,如设置为“仅联系人”,而接收方不在联系人列表中。
    • 应用配置错误,如未在Info.plist中正确设置NSAirDropSharingEnabled,或者文件类型未正确注册。
    • 网络干扰,周围的无线信号干扰可能导致 AirDrop 传输失败。
  2. 解决方法

    • 检查设备的 AirDrop 设置,将其设置为“所有人”(在测试或合适场景下),确保接收方设备可被发现。
    • 再次确认 Xcode 项目的Info.plist文件中NSAirDropSharingEnabled已正确设置,并且CFBundleDocumentTypes中文件类型注册无误。
    • 尝试在不同的网络环境下进行 AirDrop 操作,避免在干扰较大的环境中使用。例如,远离其他无线设备或信号源。

五、性能优化与最佳实践

(一)Handoff性能优化

  1. 减少活动数据量: 在设置NSUserActivity的用户信息时,尽量只包含必要的数据。过多的数据会增加设备间传输和同步的负担,影响 Handoff 的速度。例如,在笔记应用中,只传递笔记的关键信息,如标题和部分摘要,而不是整个笔记的大篇幅内容。当在目标设备上恢复活动时,再根据需要从服务器或本地存储加载完整内容。
- (void)prepareForHandoff {
    NSUserActivity *currentActivity = self.window.rootViewController.userActivity;
    if (currentActivity.activityType == @"com.example.notesapp.editnote") {
        Note *currentNote = self.currentlyEditingNote;
        [currentActivity setUserInfo:@{@"noteTitle": currentNote.title, @"noteSummary": [self getNoteSummary:currentNote.content]}];
    }
}
  1. 优化活动更新频率: 避免过于频繁地更新NSUserActivity。每次更新活动都可能触发系统的同步操作,增加资源消耗。例如,在笔记应用中,可以设置一个定时器,在用户停止编辑一段时间后再更新活动,而不是每次字符输入都更新。
@property (nonatomic, strong) NSTimer *updateActivityTimer;

- (void)noteContentDidChange {
    if (self.updateActivityTimer) {
        [self.updateActivityTimer invalidate];
        self.updateActivityTimer = nil;
    }
    self.updateActivityTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(updateUserActivityAfterDelay) userInfo:nil repeats:NO];
}

- (void)updateUserActivityAfterDelay {
    NSUserActivity *currentActivity = self.window.rootViewController.userActivity;
    if (currentActivity.activityType == @"com.example.notesapp.editnote") {
        Note *currentNote = self.currentlyEditingNote;
        [currentActivity setUserInfo:@{@"noteTitle": currentNote.title, @"noteContent": currentNote.content}];
        [currentActivity updateUserActivity];
    }
    self.updateActivityTimer = nil;
}

(二)通用剪贴板最佳实践

  1. 数据格式兼容性: 在复制数据到剪贴板时,优先选择通用的数据格式。对于文本,使用 UTF - 8 编码的字符串;对于图像,选择常见的格式如 PNG 或 JPEG。这样可以确保在不同设备和应用间更好地共享数据。例如,在复制图像前进行格式转换:
UIImage *originalImage = [UIImage imageNamed:@"sampleImage"];
NSData *imageData = UIImagePNGRepresentation(originalImage);
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.data = imageData;
  1. 避免频繁操作: 频繁地访问剪贴板会影响性能,特别是在循环或高频率的用户操作场景下。尽量合并复制和粘贴操作,减少对剪贴板的读写次数。例如,如果需要多次复制不同内容到剪贴板,可以先将这些内容存储在一个临时数组中,最后一次性更新剪贴板。

(三)AirDrop性能优化

  1. 文件预处理: 在通过 AirDrop 发送文件前,对文件进行必要的预处理。例如,对于大文件,可以进行压缩以减少传输时间和数据量。对于图像文件,可以调整分辨率和质量,在保证基本质量的前提下减小文件大小。
NSURL *fileURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"largeFile" ofType:@"pdf"]];
NSData *fileData = [NSData dataWithContentsOfURL:fileURL];
NSData *compressedData = [self compressData:fileData];
NSURL *compressedFileURL = [self saveCompressedData:compressedData];
UIDocumentInteractionController *interactionController = [UIDocumentInteractionController interactionControllerWithURL:compressedFileURL];
interactionController.delegate = self;
[interactionController presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];
  1. 优化用户体验: 在 AirDrop 操作过程中,给用户提供清晰的反馈。例如,显示进度条表示文件传输进度,当传输完成或失败时,弹出相应的提示框告知用户。可以使用UIDocumentInteractionController的代理方法来实现这些功能。
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"AirDrop Completed" message:@"The file has been successfully sent." preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:okAction];
    [self presentViewController:alertController animated:YES completion:nil];
}

- (void)documentInteractionController:(UIDocumentInteractionController *)controller didFailToSendWithError:(NSError *)error {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"AirDrop Failed" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:okAction];
    [self presentViewController:alertController animated:YES completion:nil];
}

通过以上对 Objective - C 中 Handoff 与 Continuity 功能开发的详细介绍、代码示例、常见问题解决以及性能优化,开发者能够更全面地掌握这些功能的实现,为用户打造更加无缝、高效的苹果生态应用体验。在实际开发中,还需要根据应用的具体需求和场景进行灵活调整和优化,以充分发挥这些功能的优势。