You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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新增了对分节和搜索的支持,只需对现有代码做以下调整:

关键修改点

  1. 采用SectionedEntityQuery协议:替代基础的EntityQuery,通过resultEntities()方法返回分节的实体集合。
  2. 实现搜索逻辑:添加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:28:15