WWDC22: App 通过专注模式过滤器设置可显示的内容
自 iOS15 开始引入专注模式,系统会帮助用户在一段时间内专注于重要的事情。iOS 16 增强了这一功能:使用专注模式过滤器对通知、App 进行设置。
对 App 意味着什么
- 有时 App 可能处理多个登录账号,那现在可以根据系统场景(专注模式)控制可显示的账号内容。
- 过滤内容,如日历应用,在专注模式下,只会显示工作任务。
- 过滤通知,如邮件应用,回家后不会推送与工作相关的邮件通知。
- 甚至可以在专注模式下改变应用的外观、布局。
总之,使用专注模式过滤器,App 可以在不同场景下根据用户设置显示不同的内容,改善用户体验。
哪些内容可以自定义
这取决于 App 提供的参数。
- 使用
AppIntent
提供可以自定义的属性,了解 AppIntents 框架 - 属性在系统专注模式设置中作为过滤器显示出来
- 用户在专注模式设置中设置过滤器来设定 App 的运作方式
实践
实现 SetFocusFilterIntent
// 1. 导入 AppIntents framework
import AppIntents
// 2. 定义一个实现了 `SetFocusFilterIntent` 的结构体
struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
// 3. 设置标题
static var title: LocalizedStringResource = "Set account, status & look"
// 4. 和描述
static var description: LocalizedStringResource? = """
Select an account, set your status, and configure
the look of Example Chat App.
"""
}
以上内容会结合 App 的图标,一起出现在系统的专注模式过滤器的设置中,以网格的形式呈现。注意标题和描述字符串都是静态的,系统在安装您的应用时就读取了他们,定义参数后可以动态设置显示内容。
定义参数
- 定义专注模式过滤器时需要提供一系列修饰为参数(
@Parameter(title:, default:)
)的属性,用于指定用户可以自定义的内容。 - 参数需要命名并确定数据类型,可以是标准数据类型如 Bool, string, float 等
- 自定义数据类型需要设定为实体
Entity
,即可以修饰为参数使用了,参考 https://developer.apple.com/videos/play/wwdc2022/10032 - 参数可以被标记为可选,非可选的参数需要提供一个默认值
import AppIntents
struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
// 修饰为参数的属性,类型为 Bool,显示标题为 `Use Dark Mode` 用来描述参数
// 非可选,默认值 `false`
@Parameter(title: "Use Dark Mode", default: false)
var alwaysUseDarkMode: Bool
// 可选 String 参数,没有默认值
@Parameter(title: "Status Message")
var status: String?
// 可选 AccountEntity 实体参数
@Parameter(title: "Selected Account")
var account: AccountEntity?
// ...
}
动态设置显示内容
用户设置过滤器后,系统过滤器设置页应该根据用户设置动态显示内容
struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
// ...
// 一个显示给用户的动态文本片段,根据上文的参数动态返回
var localizedDarkModeString: String {
return self.alwaysUseDarkMode ? "Dark" : "Dynamic"
}
// 需要实现的属性,返回一个包含标题和副标题的 DisplayRepresentation 对象
var displayRepresentation: DisplayRepresentation {
// 计算内容
var titleList: [LocalizedStringResource] = [], subtitleList: [String] = []
if let account = self.account {
titleList.append("Account")
subtitleList.append(account.displayName)
}
if let status = self.status {
titleList.append("Status")
subtitleList.append(status)
}
titleList.append("Look")
subtitleList.append(self.localizedDarkModeString)
let title = LocalizedStringResource("Set \(titleList, format: .list(type: .and))")
let subtitle = LocalizedStringResource("\(subtitleList.formatted())")
// 返回 DisplayRepresentation 对象
return DisplayRepresentation(title: title, subtitle: subtitle)
}
// ...
}
响应用户设置
用户在系统过滤器设置中完成配置后,系统会在专注模式发生变化时通知你的应用:
- 应用运行中,
FocusFilterIntent
会收到perform
方法的调用 - 没有运行,可以部署一个扩展,系统会在扩展中调用
perform
方法
所以 App 和扩展都可以调用 perform
方法,通常来讲,如果只是随着专注模式更改应用的界面外观,只需要在 App 内实现 perform
方法就可以了;如果你的 App 包含小组件、通知、标记这些都需要根据专注模式改变,那就需要部署一个扩展了,也就是说当应用需要更新任何内部视图之外的东西,就需要部署一个扩展了。
perform
方法
同上,perform
方法也需要在 App 的 focus filter 中实现
import AppIntents
struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
// ...
func perform() async throws -> some IntentResult {
// 定义一个 App 内的数据
let myData = AppData(
// 通过 `self.参数名` 的方式调用参数
alwaysUseDarkMode: self.alwaysUseDarkMode,
status: self.status,
account: self.account
)
// 更新应用
myModel.shared.updateAppWithData(myData)
// 返回
return .result()
}
// ...
}
但专注模式发生变化或过滤器参数发生变化时,系统就会调用perform
方法,更改 app 的配置,随后可以依此更改 App 的行为和外观。
有时需要主动查询当前的过滤器参数,可以访问 current
属性,例如示例中调用 ExampleChatAppFocusFilter.current
import AppIntents
func updateCurrentFilter() async throws {
do {
let currentFilter = try await ExampleChatAppFocusFilter.current
let myData = AppData(
myRequiredBoolValue: currentFilter.myRequiredBoolValue,
myOptionalStringValue: currentFilter.myOptionalStringValue,
myOptionalAppEnum: currentFilter.myOptionalAppEnum,
myAppEntity: currentFilter.myAppEntity
)
myModel.shared.updateAppWithData(myData)
} catch let error {
print("Error loading current filter: \(error.localizedDescription)")
throw error
}
}
App context
如果要在 App 视图之外响应专注模式过滤器的设置,例如过滤通知信息,设定 App 通知标记的数量,专注模式下只显示选中帐号相关的通知,就需要提供额外的上下文信息返回给系统。
提供方法:
- 创建
AppContext
对象,作为perform
函数结果的一部分返回给系统。 - 可以在专注模式过滤器中随时返回
AppContext
,并通过调用invalidate
强制系统获取更新值。
以过滤通知为例,为了决定在专注模式下哪些通知可以打断用户并被显示出来,需要在 AppContext
设置 filterPredicate
属性,即“过滤器谓词”,同时需要设置 UNNotification
一个新的字符串属性 filterCriteria
,如果通知的过滤器与过滤器谓词不匹配,该通知就会被静音。
import AppIntents
struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
// 在 Intent 中设置 appContext
var appContext: FocusFilterAppContext {
// 设置一个过滤器谓词,匹配被允许的帐号标识符
let allowedAccountList = [account.identifier]
let predicate = NSPredicate(format: "SELF IN %@", allowedAccountList)
// 返回一个包含过滤器谓词的 `FocusFilterAppContext` 对象,但通知不是来自个人账户时就会被静音
return FocusFilterAppContext(notificationFilterPredicate: predicate)
}
}
// 发送一条会被静音的工作通知
let content = UNMutableNotificationContent()
content.title = "Curt Rothert"
content.subtitle = "Slide Feedback"
content.body = "The run through today was great. I had few comments about slide 22 and 28."
// 设置标识符,即上文用到的 `account.identifier`,但这是一个工作帐户的标识符,所以会被静音
content.filterCriteria = "work-account-identifier"
以上示例为本地通知,如果是远程通知可以在 JSON payload 字段中进行配置。
如果是设置通知的标记数量,可以使用新的 UserNotifications API,在 UNUserNotificationCenter
上调用 setBadgeCount
方法提供一个无符号整数即可。
本文根据 WWDC 22 Meet Focus filters 总结:https://developer.apple.com/videos/play/wwdc2022/10121/