让你的 App 支持快速备忘录
Contents
快速备忘录是什么?
快速备忘录本质是备忘录应用的一个扩展,它的目标是方便用户在已支持快速备忘录功能的 App 里(如 Safari)快速记录内容,目前支持内容的格式包括,文字、图片、地图、链接(以卡片的形式)。并且在再次回到上次发生过记录行为的地方会有高亮提示(称之为快速备忘录建议),方便回顾和修改整理。再搭配这次 iOS 15/ macOS Monterey 升级的备忘新功能——支持的 @能力、# tag 、搜索、分享能力,让 Apple 设备上的资料整理变的前所未有的高效。
快速备忘录如何使用
我们以在 macOS Monterey 上使用为例(本 session 里是使用支持 Apple Pencil 的 iPad 演示,原理一样)。任一设备上创建的 Notes 都是跨平台共享的,在 iPad 上创建的 Notes 可以在 macOS 、iPhone 上看到。如何使用?
- 桌面唤醒 按住 Command 键,将鼠标置于整个屏幕右下角(双屏的用户主屏幕的右下角),此时会展示小卡片,

这个行为是可以配置的,路径是
Preferences -> Mission Control而不是 Preferences -> Screen Saver 的 Screen Hot Corners.

- 在 Safari 里唤醒(目前 beta 版本只有 Safari 支持)
也有两种方式,一种是选中文字(注意不能选中编辑状态的文字),点击右键菜单,
选中
New Quick Note,Add To Quick Note,此时会唤起右下角悬浮卡片(此时不能打开 Notes App,否则只会直接打开 Notes App,而不是悬浮卡片)。
可以看到对双屏用户,悬浮菜单的位置不对,我在上面的屏幕操作,悬浮卡片在下面屏幕。点击悬浮卡片,展开悬浮的备忘编辑器(这时候悬浮编辑器又是在上面的屏幕,位置是对的)。

这里的
Add To Quick Note行为不符合常理,按照我的理解:应该是添加到这个页面已有的备忘才对;而事实的行为是它会添加的你上一个备忘里,没有区分是否是相同 URL 的页面里选中的文字(猜测和 Safari 15 对 Quick Notes 的适配逻辑有关系)
第二种添加方式,在新版本浏览器的地址栏,点击更多按钮,选中Create Quick Note,

很遗憾,点击后在我的电脑上没有什么反应,什么都没有发生,期待下次 beta 能够 work。
快速备忘录和备忘录的关系
简单是说 快速备忘录是备忘录的一个快捷入口,被保存的快速备忘录最后会变成备忘录的一个新分组Quick Notes,展示在所有分组最上面;所有 Quick Notes 和其他备忘录里的 Notes 没有区别(除了在 Notes App 里不能被锁定外)。另外本次 macOS Menterey 还可以通过 iPhone 在备忘里增加手写体,保存为图片和 Apple Pencil 的输出一样。

快速备忘录和备忘录 App 是独立的窗体,如果你已经打开了 A,想回到备忘录 App,iPad 上可以点击右上角工具栏里的 Notes 图标,但是在 macOS 上我没找到任何途径,除非手动在菜单里选择 Window -> Notes。
快速备忘录支持的格式包括文字、图片、地图、链接(以卡片的形式),而备忘录 App 支持的格式远不止如此,Objective-c 源码、mp4 源文件等大部分支持 URI 访问的资源都可以支持,他们会被当作附件的方式保存在备忘的文件系统里。

更牛的是这些视频和源码都会被自动同步到云端(多端冲突处理逻辑待查),用户完全不用关心被保存在哪儿,所以可以放心的把它当作富邮件客户端使用了。
快速备忘录的工作原理
快速备忘录技术上沿用里 NSUserActivity 的接口,而 NSUserActivity 之前被广泛使用在其他保持 App 的运行态的功能里,如 Handoff \ Universal Link \ Widget 打开 Host App 流程等。源 App 将现在正在发生的事件,用 NSUserActivity 的类型封装这些状态,生成实例,注册到 User activity system系统。而 User activity system系统管理着这些 NSUserActivity 实例,传递给目标 App,目标 App 接收到这些 NSUserActivity 实例后作出相应的逻辑处理——相信上面的这些步骤你已经很熟悉了。
当快速备忘录加入到 User activity system系统后,系统还会把这些 NSUserActivity 实例同时发送给快速备忘录,这时候会触发你的 App 的 Add Link 菜单和快速备忘录建议。

支持快速备忘录的关键字段
快速备忘录复用了 NSUserActivity 对象里业已存在的 3 个字段来支持功能实现;
- targetContentIdentifier
- persistentIdentifier
- webpageURL
不同的功能使用不同的字段;
- targetContentIdentifier也用于 App 的状态恢复,服务于多窗口、多任务时的内容接力;
- persistentIdentifier也用于 Spotlight 里用来标识应用的内容,正如其名称所指,它服务于被持久化的内容,系统还提供了根据此字段删除 NSUserActivity 实例的接口;
- webpageURL表明源 App 活动内容是个网页,用于
Handoff、Universal Link等场景;
为了能够支持快速备忘录能够实现跨平台的体验,以上字段的值需要满足一定的要求;
- 唯一性,在多次内容的收集中需要有不同的 id;需要注意不要和其他 App 的值冲突;
- 全局性,除了考虑当前设备外,还需要考虑多个设备上相同内容里生成的值的唯一性;
- 稳定性,今天在本 App 里生成的内容,在半年之后,重新打开相同界面能够使用相同的标识来保存快速备忘录。这也是快速备忘录建议能够出现的前提条件。如果你用过类似 Safari 插件
Liner或者LightNote for macOS, 你在某个网页之前收集的文字,下次打开后会高亮显示,容许你再次编辑修改。
如何配置 App 支持 快速备忘录
下面我新建 QuickNote 的 iOS 应用来演示如何配置。
1. 声明支持 ActivityType 类型
在 info.plist 文件里声明 ActivityType,以让User activity system系统过滤掉本 App 不关心的 NSUserActivity 的实例,也防止信息泄漏。通常这些可以使用 bundleId 作为前缀作为唯一性保证。

2. 注册本 App 的活动状态
在合适的时机,创建描述当前活动状态的 NSUserActivity 对象实例,注册到User activity system系统。
|
|
当前活动的管理可以让 UIKit 来帮我们在合适的时机注册。上面例子则表示是 ViewController 活动时激活、销毁后去掉激活还可以手动管理。在特殊的逻辑里,也可以自己手动管理。如 Session 里举的例子,图片浏览器使用过程中,当前查看的图片即为最新活动内容。
|
|
3. 接收第三方或者其他设备传入的 NSUserActivity 对象
具体做法是实现和 userActivity 相关的 UIWindowSceneDelegate 的几个方法;
|
|
我按照 session 的步骤完成了 demo,一个 iOS 应用,一个 macOS 应用。百般调试没发现如何在 App 里激活/ 唤起 快速备忘录的悬浮窗里的 Add Link;经过网络搜索发现也有很多人有相同的疑问,个别能成功的也是在类似演示中的 Apple Pencil + iPad 场景;详细见论坛讨论,如果你已经实现了麻烦也留言给我;如果我找到了途径或者 macOS beta 更新后能够实现,我会第一时间更新在 demo 工程里。
适配快速备忘录的最佳实践
NSUserActivity 是 App 活动转发的网关,它也是其他一些特性的基础。为了支持快速备忘录而实现的上述接口,还能得到其他一些特性加持——默认 handoff 上启用的,其它特性也可按需求加入(比如 Siri 建议、Siri 提醒等),比如你的 App 支持使用 App 内的文件生成提醒;或者让你 App 内发表的 blog 出现在 Spotlight 的搜索结果里。
而支持 NSUserActivity 有一些最佳实践想和大家分享,共包含 4 块内容;
1. 标题
标题是供人阅读的,需要对备忘的内容更强概括和描述,这些标题会在 Add Link 菜单里展示,通常而言我们使用文章或者网页的标题就可以了。
2. 标识
指 targetContentIdentifier,persistentIdentifier。如何取一个唯一、全局、长期稳定有效的标识?比分说避免使用和设备相关的数据;不要使用转瞬即逝的状态信息,如 session ID、某个特定视图的属性;图片自身的名字也不是个长期稳定的标识,因为可能会被修改,这样做无法保证返回到上个界面时,这些名称还依然存在。如果说图片,可以考虑使用 App 保存的 UUID 作为图片的标识,即使内容被移动到别的地方依然能够重新定位到。
通常,URL 是不错的唯一性标识,但是它有时候会代表一些临时存在的信息,不符合上述里描述的稳定性原则。如果 webpageURL 符合标识的要求,你也可以放心使用。但快速备忘录里优先选择用 targetContentIdentifier,persistentIdentifier两个字段,尤其是如果 NSUserActivity 被用于状态恢复和 Spotlight 时,建议这两个字段都设置上,设置为相同的值。如果正好这个场景有对应的页面程序(如电商 App 里的详情页面),那么把 webpageURL 作为兜底设置,以便当 App 没有安装时,可以打开对应的网页。
3. 持续更新活动的状态
另一个重要的实践是确保应用的当前 NSUserActivity 实例是最新的。这意味着要跟上正在发生的事情。最佳实践是在检测到任何活动更改时,也要随之更新标题和标识符,保持属性的准确性,比如选择查看不同的照片。不建议复用 NSUserActivity 实例。当有新内容时,比如新照片,创建一个新 NSUserActivity 。
为了持续更新活动的状态,我们最容易想到是每次状态修改时,修改 NSUserActivity 对象,如在地图应用中,用户的缩放比例和查看的位置被保持在 NSUserActivity 对象的 userInfo 对象里。但是随着每次用户操作都去更新这两个值,是个不小的开销,这时候,我们可以使用 needsSave字段。这样当User activity system系统需要传递或者持久化 NSUserActivity 对象时,会回调一个接口来询问最新状态,这是去更新缩放比例和位置的最佳时机。
|
|
iOS 里类似性能优化的设计如,
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(7.0))和@property (nonatomic) CGFloat estimatedRowHeight API_AVAILABLE(ios(7.0))
4. 版本兼容性问题
随着 App 的更新,需要考虑以下两种情况;
- 新版本支持旧版本的链接
- 旧版本需要不认识的链接,丢弃而不是触发崩溃; 当遇到 link 丢失或者找不到时,需要显示合适的错误信息,以及默认的逻辑行为,提供用户体验。
适配快速备忘录让你的 App 内容融入到系统里的备忘工作流中,紧密的串联起人、内容和你的 App。确保你已经适配了 NSUserActivity,现在正是重新检查已有代码,提升他们到新高度的好机会。设置唯一、全局、稳定的标识,把 NSUserActivity 交给合适的响应者,让系统管理当前的 NSUserActivity ,处理其他所有的工作。
如何看待快速备忘录的推出
任何一个平台在迭代的时候都会考虑两个来源的声音:1. 团队内部的路线图。2. 社区的呼声。而快速备忘录正是苹果社区考察后选择里 Apple 生态圈里有广泛用户使用需求,但是限于第三方服务,没有到达苹果标准的资料收集类 App 作为标的。实现网页收集资料的需求,Chrome、Firefox 上有大量的插件,我使用过很多,大部分还属于实验室作品,体验极差。而 Safari 上 Liner,是最靠近我需要的插件,但是网页访问速度慢、还要收费;所以我去年在 WWDC 后用它推荐的 JS SDK 参考 Liner 做了一个 Safari 插件 LightNote,利用苹果的 iCloud Drive 存储,在本地开服务器实现了个简化版。
而今年的快速备忘录推出,完全宣判了 LightNote 类 App 的死刑。快速备忘录的服务可不仅仅是网页,还支持把 App 内的内容也纳入到资料池里,尤其是它拥有系统级的快捷入口。而留给 Liner 和 LightNote 的出路,可能就是内容聚合和多浏览器、多平台支持这些特性了。 其实那些做 OCR、翻译、天气的 App 在 iOS 15 里遭遇了相同的命运,这也是一个生态发展的无奈、必经之路。
目前的 BUG
- Safari 里重新打开任意页面都会出现左下角悬浮窗,应该是只有我生成过快速备忘录的页面出现才对
- 在 iPad 上,添加到 快速备忘录也是失效的,点了没有反应
- 在非 iPad Pro (和 Apple Pencil)之外,不知道怎么触发
Add Link能力。iPhone 里也支持 快速备忘录?怎么使用,难以想象,希望官方能够给个例子。
附录
Author hite
LastMod 2021-06-29