Reputation: 1036
I'm currently developing an application using SwiftUI
and trying to make a widget ios 14
user can choose a data to check its detail using IntentConfiguration
An outline I want to do in this App is like below:
UUID
, task: String
, status: String
, and some...) to
CoreData
in Host AppIn my codes, I could implement almost all the functions I explain above.
But I don't know How can I display task data in the edit widget...
So far, I specify UUID data in CoreData
as a Parameter in Configuration. Because WidgetEntryView
needs UUID
(or something unique value) to filter which row
requests to CoreData
and to make URL
for DeepLink
to detail view in host App.
So the list view in the widget displays UUID data like below.
But I want to display task data instead of UUID in that list view keeping to give UUID data to the Widget view as well.
If I specify task data in CoreData
as a Parameter in Configuration, the widget displays tasks as a list. But in that case a filter for request data to Coredata
(NSPredicate(format: "id == %@"
) and widgetURL()
don't work...
How could I implement this?
Here are the codes:
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 (Host App)
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: Version 12.0.1
iOS: 14.0
Life Cycle: SwiftUI App
Upvotes: 1
Views: 612
Reputation: 54591
You can create a custom type for your configuration parameter. Currently you're using String
which limits you to one value only.
Instead create a custom type, let's call it Item
:
Now you have the identifier
and displayString
properties for your Item
which can be mapped to the UUID
and task
properties of your model.
Then, in the IntentHandler
instead of INObjectCollection<NSString>?
you need to provide INObjectCollection<Item>?
in the completion.
Assuming you already have your results
fetched from Core Data, you only need to map them to the Item
objects:
let results = try moc.fetch(request)
let items = results.map {
Item(identifier: $0.id.uuidString, display: $0.task)
}
completion(items, nil)
This way you can use the display
property to show readable information to the user but also have the identifier
property which can be later used to retrieve the Core Data model.
Upvotes: 1