iOS 17中如何用AppIntent实现Widget动态数据选择?
能否通过AppIntent实现Widget编辑组件的多节可搜索效果?
iOS 17之前的实现方案
在iOS 17之前,实现Widget「编辑组件」的动态数据功能需要以下两步:
- 使用intent定义文件
- 使用intent扩展
完成后可获得以下效果:
- 可搜索的视图控制器
- 多节数据视图控制器
Intent定义文件 + Intent扩展代码
class IntentHandler: INExtension, ConfigurationIntentHandling { func provideStickyNoteWidgetItemOptionsCollection(for intent: ConfigurationIntent, with completion: @escaping (INObjectCollection<StickyNoteWidgetItem>?, Error?) -> Void) { var stickyNoteWidgetItems = [StickyNoteWidgetItem]() var archivedStickyNoteWidgetItems = [StickyNoteWidgetItem]() let allStickyNoteWidgetItems = NSPlainNoteRepository.getStickyNoteWidgetItemsWithoutTrash() for allStickyNoteWidgetItem in allStickyNoteWidgetItems { if allStickyNoteWidgetItem.archived == 1 { archivedStickyNoteWidgetItems.append(allStickyNoteWidgetItem) } else { stickyNoteWidgetItems.append(allStickyNoteWidgetItem) } } var sections = [INObjectSection<StickyNoteWidgetItem>]() if !stickyNoteWidgetItems.isEmpty { let section = INObjectSection(title: nil, items: stickyNoteWidgetItems) sections.append(section) } if !archivedStickyNoteWidgetItems.isEmpty { let archivedSection = INObjectSection(title: "archive".localized, items: archivedStickyNoteWidgetItems) sections.append(archivedSection) } let collection = INObjectCollection(sections: sections) completion(collection, nil) } override func handler(for intent: INIntent) -> Any { // This is the default implementation. If you want different objects to handle different intents, // you can override this and return the handler you want for that particular intent. return self } }
iOS 17中AppIntent的当前现状
在iOS 17中使用AppIntent时,基础实现仅能达到以下有限效果:
- 不可搜索
- 无法分节
AppIntent基础实现代码
import Foundation import AppIntents @available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) struct StickyNoteWidgetItemAppEntity: AppEntity { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "StickyNoteWidgetItem") @Property(title: "archived") var archived: Bool? struct StickyNoteWidgetItemAppEntityQuery: EntityQuery { func entities(for identifiers: [StickyNoteWidgetItemAppEntity.ID]) async throws -> [StickyNoteWidgetItemAppEntity] { // TODO: return StickyNoteWidgetItemAppEntity entities with the specified identifiers here. return [] } func suggestedEntities() async throws -> [StickyNoteWidgetItemAppEntity] { // TODO: return likely StickyNoteWidgetItemAppEntity entities here. // This method is optional; the default implementation returns an empty array. return [ StickyNoteWidgetItemAppEntity(id: "id0", displayString: "note 0"), StickyNoteWidgetItemAppEntity(id: "id1", displayString: "note 1"), StickyNoteWidgetItemAppEntity(id: "id2", displayString: "note 2") ] } } static var defaultQuery = StickyNoteWidgetItemAppEntityQuery() var id: String // if your identifier is not a String, conform the entity to EntityIdentifierConvertible. var displayString: String var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(displayString)") } init(id: String, displayString: String) { self.id = id self.displayString = displayString } }
问题:能否通过AppIntent实现旧版的多节可搜索效果?
可行。iOS 17为AppIntent新增了对分节和搜索的支持,只需对现有代码做以下调整:
关键修改点
- 采用
SectionedEntityQuery协议:替代基础的EntityQuery,通过resultEntities()方法返回分节的实体集合。 - 实现搜索逻辑:添加
entities(matching:)方法,处理用户的搜索请求并返回匹配结果,系统会自动在编辑界面生成搜索框。
修改后的完整代码
import Foundation import AppIntents @available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) struct StickyNoteWidgetItemAppEntity: AppEntity { static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "便签组件项") @Property(title: "已归档") var archived: Bool? struct StickyNoteWidgetItemAppEntityQuery: SectionedEntityQuery { // 根据ID获取对应实体 func entities(for identifiers: [StickyNoteWidgetItemAppEntity.ID]) async throws -> [StickyNoteWidgetItemAppEntity] { // 替换为实际根据ID查询数据的业务逻辑 return [] } // 处理搜索请求,返回匹配的实体 func entities(matching query: String) async throws -> [StickyNoteWidgetItemAppEntity] { let allItems = NSPlainNoteRepository.getStickyNoteWidgetItemsWithoutTrash() return allItems .filter { $0.displayString.localizedCaseInsensitiveContains(query) } .map { StickyNoteWidgetItemAppEntity( id: $0.id, displayString: $0.displayString, archived: $0.archived == 1 ) } } // 返回分节的实体集合,复刻旧版的分节逻辑 func resultEntities() async throws -> [EntitySection<StickyNoteWidgetItemAppEntity>] { let allItems = NSPlainNoteRepository.getStickyNoteWidgetItemsWithoutTrash() .map { StickyNoteWidgetItemAppEntity( id: $0.id, displayString: $0.displayString, archived: $0.archived == 1 ) } let activeItems = allItems.filter { $0.archived == false } let archivedItems = allItems.filter { $0.archived == true } var sections = [EntitySection<StickyNoteWidgetItemAppEntity>]() if !activeItems.isEmpty { sections.append(EntitySection(title: nil, items: activeItems)) } if !archivedItems.isEmpty { sections.append(EntitySection(title: "归档".localized, items: archivedItems)) } return sections } } static var defaultQuery = StickyNoteWidgetItemAppEntityQuery() var id: String var displayString: String var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(displayString)") } init(id: String, displayString: String, archived: Bool? = nil) { self.id = id self.displayString = displayString self.archived = archived } }
效果说明
- 分节:通过
resultEntities()返回的EntitySection数组,系统会自动渲染多节列表,和旧版的INObjectCollection效果一致。 - 搜索:实现
entities(matching:)后,Widget编辑界面会自动出现搜索框,用户输入的内容会传入该方法进行匹配筛选。
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng
相关产品推荐
相关产品推荐

