Code snippets for the app intents discussed above:
// This is the problematic intent that consistently fails.
struct HabitEntryAppIntent: AppIntent {
static let openAppWhenRun: Bool = false
static let title = LocalizedStringResource("Add a Habit Entry", table: "AppIntents")
static let description = IntentDescription(
LocalizedStringResource("Log an entry after completing a task.", table: "AppIntents"),
categoryName: LocalizedStringResource("Log Habits", table: "AppIntents"),
searchKeywords: [LocalizedStringResource("log", table: "AppIntents")],
resultValueName: LocalizedStringResource("Habit", table: "AppIntents")
)
static var parameterSummary: some ParameterSummary {
Summary("Add an entry to \(\.$habitEntity)", table: "AppIntents")
}
@Parameter(
title: LocalizedStringResource("Habit", table: "AppIntents"),
requestValueDialog: .init(LocalizedStringResource("Which habit would you like to add an entry to?", table: "AppIntents"))
) var habitEntity: HabitEntity
@MainActor func perform() async throws -> some ProvidesDialog & ReturnsValue<HabitEntity> {
let context = ModelContainer.shared.mainContext
let logger = AppIntentHabitEntryLogger()
try logger.addEntry(for: habitEntity.id, in: context)
let localizedActionName = String(localized: "Logged “\(habitEntity.name)” Entry")
context.undoManager?.setActionName(localizedActionName)
WidgetManager.shared.reload(.all)
return .result(
value: habitEntity,
dialog: .init(LocalizedStringResource("OK, added an entry to “\(habitEntity.name)”.", table: "AppIntents"))
)
}
}
extension HabitEntryAppIntent {
init(habit: HabitEntity) {
self.habitEntity = habit
}
}
// This is the non-problematic intent that consistently suceeds.
struct HabitEntryCounterForTodayAppIntent: AppIntent, AppIntentDialogDateFormatting {
static let openAppWhenRun: Bool = false
static let title = LocalizedStringResource("Number of Entries Logged in a Habit Today", table: "AppIntents")
static let description = IntentDescription(
LocalizedStringResource("The total number of entries logged so far today for a habit.", table: "AppIntents"),
categoryName: LocalizedStringResource("Habit Stats", table: "AppIntents"),
searchKeywords: [LocalizedStringResource("count", table: "AppIntents")],
resultValueName: LocalizedStringResource("Habit Statistics", table: "AppIntents")
)
static var parameterSummary: some ParameterSummary {
Summary("Get the total number of \(\.$habitEntity) entries logged so far today", table: "AppIntents")
}
@Parameter(
title: LocalizedStringResource("Habit", table: "AppIntents"),
requestValueDialog: .init(LocalizedStringResource("Which habit would you like to count entries in?", table: "AppIntents"))
) var habitEntity: HabitEntity
@MainActor func perform() async throws -> some ProvidesDialog & ReturnsValue<Int> {
let habitID = habitEntity.id
let context = ModelContainer.shared.mainContext
let date = Date.now
let startOfToday = Calendar.shared.startOfDay(for: date)
let startOfTomorrow = Calendar.shared.startOfNextDay(for: date)
var descriptor = FetchDescriptor<HabitEntry>(predicate: #Predicate {
($0.habitID == habitID) && ($0.date >= startOfToday) && ($0.date < startOfTomorrow) && ($0.count > 0)
})
descriptor.propertiesToFetch = [\HabitEntry.count]
let matches = try context.fetch(descriptor)
let totalCount: Int = matches.reduce(.zero) { $0 + $1.count }
return .result(
value: totalCount,
dialog: .init(LocalizedStringResource("There are \(totalCount) entries recorded so far today for “\(habitEntity.name)”.", table: "AppIntents"))
)
}
}
extension HabitEntryCounterForTodayAppIntent {
init(habit: HabitEntity) {
self.habitEntity = habit
}
}
// This is the code that logs the habit in the problematic intent.
@MainActor struct AppIntentHabitEntryLogger {
func addEntry(for habitID: UUID, in context: ModelContext) throws {
let currentDate = Date.now
let startOfToday = Calendar.shared.startOfDay(for: currentDate)
let startOfTomorrow = Calendar.shared.startOfNextDay(for: currentDate)
var descriptor = FetchDescriptor<HabitEntry>(predicate: #Predicate {
($0.habitID == habitID) && ($0.date >= startOfToday) && ($0.date < startOfTomorrow)
}, sortBy: [
SortDescriptor<HabitEntry>(\.date, order: .forward) // Oldest to newest.
])
descriptor.propertiesToFetch = [\HabitEntry.count]
let matches = try context.fetch(descriptor)
// The last match is the most recent one for the current day.
if let match = matches.last {
match.increaseCount()
} else {
let habit = try Habit.match(for: habitID, in: context)
let newEntry = HabitEntry(habit: habit)
context.insert(newEntry)
}
if context.hasChanges {
try context.save()
}
}
}