Syed Tariq
Syed Tariq

Reputation: 2918

How can I access reminders in a list in a Apple Reminders App programmatically

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

Answers (1)

Syed Tariq
Syed Tariq

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

Related Questions