Skip to content

Instantly share code, notes, and snippets.

@davidsteppenbeck
Last active May 16, 2025 17:38
Show Gist options
  • Save davidsteppenbeck/50496d11babc7c1c5d2838a3c6930fcf to your computer and use it in GitHub Desktop.
Save davidsteppenbeck/50496d11babc7c1c5d2838a3c6930fcf to your computer and use it in GitHub Desktop.
Shortcuts: Invalid action metadata
// 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 succeeds.
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()
}
}
}
struct HabitShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: HabitEntryAppIntent(),
phrases: [
"Add an entry to \(\.$habitEntity) with \(.applicationName)",
"Add an entry to \(\.$habitEntity) in \(.applicationName)",
"Add entry \(\.$habitEntity) with \(.applicationName)",
"Add entry \(\.$habitEntity) in \(.applicationName)",
"Add an entry with \(.applicationName)",
"Add an entry in \(.applicationName)",
"Add an entry to \(.applicationName)",
"Log an entry with \(.applicationName)"
],
shortTitle: LocalizedStringResource("Add an Entry", table: "AppIntents"),
systemImageName: "checkmark.circle.fill",
parameterPresentation: ParameterPresentation(
for: \.$habitEntity,
summary: Summary("Add \(\.$habitEntity) entries", table: "AppIntents"),
optionsCollections: {
OptionsCollection(HabitEntityQuery(), title: LocalizedStringResource("Log Completed Habits", table: "AppIntents"), systemImageName: "checkmark")
}
)
)
AppShortcut(
intent: HabitEntryCounterForTodayAppIntent(),
phrases: [
"Count \(\.$habitEntity) entries logged today with \(.applicationName)",
"Count \(\.$habitEntity) entries logged today in \(.applicationName)",
"How many \(\.$habitEntity) entries have been logged today in \(.applicationName)?",
"Count habit entries logged today with \(.applicationName)",
"Count habit entries logged today in \(.applicationName)",
"Count entries logged today for a habit with \(.applicationName)",
"Count entries logged today for a habit in \(.applicationName)",
"Count \(.applicationName) entries logged today"
],
shortTitle: LocalizedStringResource("Count Entries Logged Today", table: "AppIntents"),
systemImageName: "number.circle.fill",
parameterPresentation: ParameterPresentation(
for: \.$habitEntity,
summary: Summary("The total number of \(\.$habitEntity) entries logged so far today", table: "AppIntents"),
optionsCollections: {
OptionsCollection(HabitEntityQuery(), title: LocalizedStringResource("Count Entries Logged Today in a Habit", table: "AppIntents"), systemImageName: "number")
}
)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment