Empower your intents
System & Services 进阶 22m

增强你的 Intent 能力

Empower your intents

2020年6月25日

在 Apple 官方观看视频

一句话判断

这场 Session 讲了如何让你的自定义 Intent 更强大——动态选项、参数联动、条件显示,让你的快捷指令不再是”填参数 → 执行”的死板流程。

这场 Session 讲了什么

自定义 Intent 的用户体验在很大程度上取决于参数的配置方式。2020 年的改进让 Intent 的参数交互更加灵活和智能。

核心更新包括:动态选项(Dynamic Options)——参数的可选值不再是固定的,可以根据上下文动态生成。比如”选择播放列表”参数可以实时从用户的账户中获取列表。参数联动(Parameter Linking)——当用户选择某个参数后,可以影响其他参数的显示和可选值。比如选择”摇滚”类型后,“艺术家”参数自动过滤为摇滚艺术家。条件显示(Conditional Display)——某些参数只在特定条件下才显示,避免界面过于复杂。

Session 还介绍了新的 Intent 编辑器功能,可以在 Xcode 中直接预览和测试 Intent 的交互流程。

值得深挖的点

动态选项的实现。 过去参数的选项列表是在 Intent 定义文件中静态配置的。现在你可以通过 INIntentHandlerProviding 协议动态提供选项。关键是在 provideXxxOptionsCollection(for:with:) 方法中根据当前上下文返回合适的选项列表。

参数联动的用户体验。 联动的关键是让用户感觉选项是”自然的”而非”受限的”。当你选择了一个城市后,餐厅列表自动更新为该城市的餐厅——这不是限制,而是帮助用户更快找到想要的。设计联动时要注意:联动方向应该是从宽到窄(先选大类再选小类),不要反向联动。

代码片段

import Intents

// 动态提供参数选项
class OrderCoffeeIntentHandler: NSObject, OrderCoffeeIntentHandling {
    
    // 动态提供咖啡店列表
    func provideStoreOptionsCollection(
        for intent: OrderCoffeeIntent,
        with completion: @escaping (INObjectCollection<CoffeeStore>, Error?) -> Void
    ) {
        // 从网络或本地数据获取咖啡店列表
        fetchStores { stores in
            let options = stores.map { store in
                CoffeeStore(
                    identifier: store.id,
                    display: store.name
                )
            }
            completion(INObjectCollection(items: options), nil)
        }
    }
    
    // 根据选择的咖啡店动态提供菜单
    func provideMenuOptionsCollection(
        for intent: OrderCoffeeIntent,
        with completion: @escaping (INObjectCollection<CoffeeItem>, Error?) -> Void
    ) {
        guard let storeId = intent.store?.identifier else {
            completion(INObjectCollection(items: []), nil)
            return
        }
        
        fetchMenu(storeId: storeId) { items in
            let options = items.map { item in
                CoffeeItem(
                    identifier: item.id,
                    display: "\(item.name) - ¥\(item.price)"
                )
            }
            completion(INObjectCollection(items: options), nil)
        }
    }
}
// 参数验证和确认
func confirm(intent: OrderCoffeeIntent, completion: @escaping (OrderCoffeeIntentResponse) -> Void) {
    // 验证参数是否完整
    guard intent.store != nil, intent.menuItem != nil else {
        completion(OrderCoffeeIntentResponse(code: .failure, userActivity: nil))
        return
    }
    
    // 检查咖啡店是否营业
    checkStoreAvailability(storeId: intent.store!.identifier!) { isAvailable in
        if isAvailable {
            completion(OrderCoffeeIntentResponse(code: .ready, userActivity: nil))
        } else {
            completion(OrderCoffeeIntentResponse(
                code: .failureRequiringAppLaunch,
                userActivity: nil
            ))
        }
    }
}

最佳实践

  • 动态选项要有合理的默认值。 第一次打开参数选择器时,如果列表为空用户会困惑。提供最近使用或最热门的选项作为默认。
  • 参数联动要清晰可预测。 用户改变一个参数后,应该能理解为什么其他参数也变了。在 Intent 定义中用 description 解释联动逻辑。
  • 动态选项的加载要快。 Shortcuts App 中参数选项加载慢会严重影响体验。使用缓存和预加载。
  • 为选项提供 subtitle 和 image。 纯文字列表单调且难以区分,加上描述和图标让选项更直观。

还有什么值得关注

  • INObject 支持 subtitle 属性,可以显示额外的描述信息
  • 选项支持分组显示(通过 INObjectCollection 的 sections)
  • 新的 Intent 编辑器支持实时预览参数联动效果
  • 自定义 Intent 的响应支持富文本展示
WWDC 2020