Как отобразить данные, относящиеся к той же строке в CoreData, в представлении виджета IntentConfiguration iOS 14?

В настоящее время я разрабатываю приложение с использованием SwiftUI и пытаюсь заставить widget ios 14 пользователя выбирать данные для проверки их деталей с помощью IntentConfiguration

Схема, которую я хочу сделать в этом приложении, выглядит следующим образом:

  1. Пользователь добавляет некоторые данные в добавляющем представлении (идентификатор: UUID, задача: String, статус: String и некоторые ...) в CoreData в хост-приложении
  2. Пользователь выбирает данные, которые он хочет проверить, в виджете редактирования.
  3. Пользователь может просмотреть краткую информацию в представлении виджета.
  4. Если пользователь коснется представления виджета, он сможет проверить подробные данные в подробном представлении в главном приложении.

В своих кодах я мог реализовать почти все функции, которые я объяснил выше.

Но я не знаю, как мне отобразить данные задачи в виджете редактирования ...

Пока что я указываю данные UUID в CoreData как параметр в конфигурации. Потому что WidgetEntryView требуется UUID (или какое-то уникальное значение), чтобы отфильтровать, какие row запросы к CoreData, и сделать URL для DeepLink для подробного просмотра в главном приложении.

Таким образом, список в виджете отображает данные UUID, как показано ниже. введите описание изображения здесь

Но я хочу отображать данные задачи вместо UUID в этом представлении списка, сохраняя также данные UUID для представления Widget.

Если я укажу данные задачи в 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, date: Date(), timerEntity: timerEntity!)
        completion(entry)
    }
    
    func getTimeline(for configuration: ConfigurationIntent, in context: Context, 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  введите описание изображения здесь

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


person Tio    schedule 10.01.2021    source источник


Ответы (1)


Вы можете создать собственный тип для своего параметра конфигурации. В настоящее время вы используете String, который ограничивает вас только одним значением.

Вместо этого создайте собственный тип, назовем его Item:

введите описание изображения здесь

Теперь у вас есть свойства identifier и displayString для вашего Item, которые можно сопоставить со свойствами UUID и task вашей модели.

Затем в IntentHandler вместо INObjectCollection<NSString>? вам нужно указать INObjectCollection<Item>? в завершении.

Предполагая, что у вас уже есть results, извлеченные из Core Data, вам нужно только сопоставить их с Item объектами:

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

Таким образом, вы можете использовать свойство display, чтобы показывать пользователю читаемую информацию, а также иметь свойство identifier, которое позже можно использовать для получения модели Core Data.

person pawello2222    schedule 10.01.2021