如何在 iOS 14 的 IntentConfiguration Widget 视图中显示与 CoreData 中同一行相关的数据?

问题描述

我目前正在使用 SwiftUI 开发应用程序,并试图让 widget ios 14 用户可以选择数据以使用 IntentConfiguration

检查其详细信息

我想在这个应用程序中做的一个大纲如下:

  1. 用户添加视图(id:UUID、任务:String、状态:String 和一些...)中添加了一些数据到 CoreData 在宿主应用程序中
  2. 用户在编辑小部件中选择要查看其详细信息的数据
  3. 用户可以在小部件视图中查看简要详细信息
  4. 如果用户点击小部件视图,可以在宿主应用程序的详细视图中查看详细数据

在我的代码中,我几乎可以实现上面解释的所有功能

但我不知道如何在编辑小部件中显示任务数据...

到目前为止,我将 CoreData 中的 UUID 数据指定为配置中的参数。因为 WidgetEntryView 需要 UUID(或一些唯一值)来过滤哪些 row 请求到 CoreData 并为 URL 制作 DeepLink 以进行详细视图在宿主应用中。

因此小部件中的列表视图显示 UUID 数据,如下所示。

enter image description here

但我想在该列表视图中显示任务数据而不是 UUID,同时将 UUID 数据提供给小部件视图。

如果我将 CoreData 中的任务数据指定为配置中的参数,小部件将任务显示为列表。但在这种情况下,过滤到 Coredata (nspredicate(format: "id == %@") 和 widgetURL() 的请求数据不起作用...

我该如何实施?


代码如下:

TimerIntentWidget.swift

import Foundation
import WidgetKit
import SwiftUI
import CoreData

struct Provider: IntentTimelineProvider {
    
    typealias Intent = ConfigurationIntent
    
    var moc = PersistenceController.shared.managedobjectContext
    
    init(context : NSManagedobjectContext) {
        self.moc = context
    }
    
    func placeholder(in context: Context) -> SimpleEntry {
        
        var timerEntity:TimerEntity?
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
       
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
        
        return SimpleEntry(configuration: ConfigurationIntent(),date: Date(),timerEntity: timerEntity!)
    }
    
    func getSnapshot(for configuration: ConfigurationIntent,in context: Context,completion: @escaping (SimpleEntry) -> ()) {
        var timerEntity:TimerEntity?
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
      
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
        
        let entry = SimpleEntry(configuration: configuration,timerEntity: timerEntity!)
        completion(entry)
    }
    
    func getTimeline(for configuration: ConfigurationIntent,completion: @escaping (Timeline<Entry>) -> ()) {
        
        var timerEntity:TimerEntity?
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
        
        request.predicate = nspredicate(format: "id == %@",UUID(uuidString: configuration.UUID!)! as CVararg)
        do{
            let result = try moc.fetch(request)
            timerEntity = result.first
        }
        catch let error as NSError{
            print("Could not fetch.\(error.userInfo)")
        }
        
        var entries: [SimpleEntry] = []
        
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour,value: hourOffset,to: currentDate)!
            let entry = SimpleEntry(configuration: configuration,date: entryDate,timerEntity: timerEntity!)
            entries.append(entry)
        }
        let timeline = Timeline(entries: entries,policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let configuration: ConfigurationIntent
    let date: Date
    let timerEntity:TimerEntity?

}

struct TimerIntentWidgetEntryView : View{
    var entry: Provider.Entry
    
    var body: some View {
        vstack{
            Text(entry.timerEntity!.id!.uuidString)
            Divider()
            Text(entry.timerEntity!.task!)
            Divider()
            Text(entry.timerEntity!.status!)
            Divider()
            Text(entry.date,style: .time)
        }
        .widgetURL(makeURLScheme(id: entry.timerEntity!.id!))
    }
}

@main
struct TimerIntentWidget: Widget {
    let kind: String = "TimerIntentWidget"
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind,intent: ConfigurationIntent.self,provider: Provider(context: PersistenceController.shared.managedobjectContext)) { entry in
            TimerIntentWidgetEntryView(entry: entry)
                .environment(\.managedobjectContext,PersistenceController.shared.managedobjectContext)
        }
        .configurationdisplayName("My Widget")
        .description("This is an example widget.")
    }
}

func makeURLScheme(id: UUID) -> URL? {
    guard let url = URL(string: "timerlist://detail") else {
        return nil
    }
    var urlComponents = URLComponents(url: url,resolvingAgainstBaseURL: true)
    urlComponents?.queryItems = [URLQueryItem(name: "id",value: id.uuidString)]
    return urlComponents?.url
}

IntentHandler.swift

import WidgetKit
import SwiftUI
import CoreData
import Intents

class IntentHandler: INExtension,ConfigurationIntentHandling {
    
    var moc = PersistenceController.shared.managedobjectContext
    
    func provideUUIDOptionsCollection(for intent: ConfigurationIntent,with completion: @escaping (INObjectCollection<Nsstring>?,Error?) -> Void) {
        
        let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
                var nameIdentifiers:[Nsstring] = []
        
                do{
                    let results = try moc.fetch(request)
        
                    for result in results{
                        nameIdentifiers.append(Nsstring(string: result.id?.uuidString ?? ""))
                    }
                }
                catch let error as NSError{
                    print("Could not fetch.\(error.userInfo)")
                }
        
                let allNameIdentifiers = INObjectCollection(items: nameIdentifiers)
                completion(allNameIdentifiers,nil)
    }
    
    override func handler(for intent: INIntent) -> Any {
        return self
    }
}

TimerIntentWidget.intentdeFinition

enter image description here

Persistence.swift(宿主应用程序)

import CoreData

class PersistenceController {
    static let shared = PersistenceController()

    private init() {}

    private let persistentContainer: NSPersistentContainer = {
        let storeURL = FileManager.appGroupContainerURL.appendingPathComponent("TimerEntity")

        let container = NSPersistentContainer(name: "ListTimer")
        container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
        container.loadPersistentStores(completionHandler: { storeDescription,error in
            if let error = error as NSError? {
                print(error.localizedDescription)
            }
        })
        return container
    }()
}

extension PersistenceController {
    var managedobjectContext: NSManagedobjectContext {
        persistentContainer.viewContext
    }
}

extension PersistenceController {
    var workingContext: NSManagedobjectContext {
        
        let context = NSManagedobjectContext(concurrencyType: .privateQueueConcurrencyType)
        context.parent = managedobjectContext
        
        return context
    }
}

import Foundation

extension FileManager {
    static let appGroupContainerURL = FileManager.default
        .containerURL(forSecurityApplicationGroupIdentifier: "group.com.sample.ListTimer")!
}

Xcode:版本 12.0.1

iOS:14.0

生命周期:SwiftUI 应用

解决方法

您可以为配置参数创建自定义类型。目前您使用的是 String,它限制您只能使用一个值。

而是创建一个自定义类型,我们称之为 Item

enter image description here

现在您拥有 identifierdisplayStringItem 属性,它们可以映射到模型的 UUIDtask 属性。>

然后,在 IntentHandler 而不是 INObjectCollection<NSString>? 中,您需要在补全中提供 INObjectCollection<Item>?

假设您已经从 Core Data 中提取了 results,您只需要将它们映射到 Item 对象:

let results = try moc.fetch(request)
let items = results.map {
    Item(identifier: $0.id.uuidString,display: $0.task)
}
completion(items,nil)

通过这种方式,您可以使用 display 属性向用户显示可读信息,但也可以使用 identifier 属性稍后用于检索核心数据模型.