Reputation: 2918
I would like to create a NSPredicate that can access reminders in a particular named list of a Reminders App. The Reminders App has the ability to create reminders in named lists. The EKReminder class (or EKCalendar that it inherits from) described in https://developer.apple.com/doocumentation/eventkit/ekreminder do not have a property that relates to the named list. Help would be appreciated particularly if includes a snippet in Swift.
Upvotes: 1
Views: 1959
Reputation: 2918
I used a well written library in Reminders-cli to find out details of the Reminders App database.
1 - All Reminder lists are stored as separate calendars. The list can be accessed by
private let Store = EKEventStore()
Store.calendars(for: .reminder)
2 - Reminders in a particular list (eg: 'Workout') can be accessed with a statement such as:
reminders.showListItems(withName: "Workout")
3 - If lists are part of a 'Group', then the first item in the list is the name of the group. It is not clear how the group name is identified as a 'Group'. If you build a Group in the Reminder App the display in the app shows the Group name with the the constituents of the Group indented.
4 - My ContentView.swift file
import SwiftUI
import EventKit
//import Reminders ( I chose to insert a copy of the library and added
//an array to store the reminders )
private var reminders = Reminders()
//private var calendar: EKCalendar?
struct ContentView: View {
private var allReminders:[EKReminder]
init() {
if Reminders.requestAccess() {
print("Access granted")
}
reminders.showLists() // prints all the lists in the Reminder database
reminders.showListItems(withName: "Workout")
self.allReminders = reminders.allReminders
print("Number of Reminders: \(self.allReminders.count)")
}
var body: some View {
NavigationView {
List {
ForEach (0 ..< self.allReminders.count) {i in
Text(self.allReminders[i].title)
}
.navigationTitle("Reminders")
}
}
}
5 - My copy of the library Reminders.swift
//
// Reminders.swift
//
// Copied from https://github.com/keith/reminders-cli
//
//
import EventKit
import Foundation
private let Store = EKEventStore()
private let dateFormatter = RelativeDateTimeFormatter()
private func formattedDueDate(from reminder: EKReminder) -> String? {
return reminder.dueDateComponents?.date.map {
dateFormatter.localizedString(for: $0, relativeTo: Date())
}
}
private func format(_ reminder: EKReminder, at index: Int) -> String {
let dateString = formattedDueDate(from: reminder).map { " (\($0))" } ?? ""
return "\(index): \(reminder.title ?? "<unknown>")\(dateString)"
}
public final class Reminders {
var allReminders: [EKReminder] = [] // added by me
public static func requestAccess() -> Bool {
let semaphore = DispatchSemaphore(value: 0)
var grantedAccess = false
Store.requestAccess(to: .reminder) { granted, _ in
grantedAccess = granted
semaphore.signal()
}
semaphore.wait()
return grantedAccess
}
func showLists() {
let calendars = self.getCalendars()
for calendar in calendars {
print(calendar.title)
}
}
func showListItems(withName name: String) {
let calendar = self.calendar(withName: name)
let semaphore = DispatchSemaphore(value: 0)
self.reminders(onCalendar: calendar) { reminders in
for (i, reminder) in reminders.enumerated() {
print(format(reminder, at: i))
self.allReminders.append(reminder) // added by me
}
semaphore.signal()
}
semaphore.wait()
}
func complete(itemAtIndex index: Int, onListNamed name: String) {
let calendar = self.calendar(withName: name)
let semaphore = DispatchSemaphore(value: 0)
self.reminders(onCalendar: calendar) { reminders in
guard let reminder = reminders[safe: index] else {
print("No reminder at index \(index) on \(name)")
exit(1)
}
do {
reminder.isCompleted = true
try Store.save(reminder, commit: true)
print("Completed '\(reminder.title!)'")
} catch let error {
print("Failed to save reminder with error: \(error)")
exit(1)
}
semaphore.signal()
}
semaphore.wait()
}
func addReminder(string: String, toListNamed name: String, dueDate: DateComponents?) {
let calendar = self.calendar(withName: name)
let reminder = EKReminder(eventStore: Store)
reminder.calendar = calendar
reminder.title = string
reminder.dueDateComponents = dueDate
do {
try Store.save(reminder, commit: true)
print("Added '\(reminder.title!)' to '\(calendar.title)'")
} catch let error {
print("Failed to save reminder with error: \(error)")
exit(1)
}
}
// MARK: - Private functions
private func reminders(onCalendar calendar: EKCalendar,
completion: @escaping (_ reminders: [EKReminder]) -> Void)
{
let predicate = Store.predicateForReminders(in: [calendar])
Store.fetchReminders(matching: predicate) { reminders in
let reminders = reminders?
.filter { !$0.isCompleted }
completion(reminders ?? [])
}
}
private func calendar(withName name: String) -> EKCalendar {
if let calendar = self.getCalendars().find(where: { $0.title.lowercased() == name.lowercased() }) {
return calendar
} else {
print("No reminders list matching \(name)")
exit(1)
}
}
private func getCalendars() -> [EKCalendar] {
return Store.calendars(for: .reminder)
.filter { $0.allowsContentModifications }
}
}
Upvotes: 1