← Back to index

CalendarManager

Spots
CodeWhat It DoesHow It Does It
▶ IMPORTS
import Foundation import EventKit import SwiftUI import CombineFramework importsImports Foundation, EventKit, SwiftUI, Combine.
class CalendarManager: ObservableObject { static let shared = CalendarManager() private let eventStore = EKEventStore() @Published var authorizationStatus: EKAuthorizationStatus = .notDetermined private init() { checkAuthorizationStatus() } // MARK: - Authorization func checkAuthorizationStatus() { authorizationStatus = EKEventStore.authorizationStatus(for: .event) } func requestAccess() async -> Bool { do { let granted = try await eventStore.requestFullAccessToEvents() await MainActor.run { checkAuthorizationStatus() } return granted } catch { print("Error requesting calendar access: \(error)") return false } } // MARK: - Calendar Events func addEntryToCalendar(_ entry: SpotEntry) async -> Result<String, CalendarError> { // Check authorization let status = EKEventStore.authorizationStatus(for: .event) switch status { case .notDetermined: let granted = await requestAccess() if !granted { return .failure(.accessDenied) } case .denied, .restricted: return .failure(.accessDenied) case .fullAccess, .authorized: break case .writeOnly: break @unknown default: return .failure(.accessDenied) } // Create the event let event = EKEvent(eventStore: eventStore) event.title = entry.playName event.startDate = entry.dateTime // Assume shows are about 2.5 hours event.endDate = entry.dateTime.addingTimeInterval(2.5 * 60 * 60) // Build location string var locationParts: [String] = [] if !entry.cuisine.isEmpty { locationParts.append(entry.cuisine) } if !entry.address.isEmpty { locationParts.append(entry.address) } event.location = locationParts.joined(separator: ", ") // Add notes with confirmation link var notes = "Added from Spots" if !entry.confirmationLink.isEmpty { notes += "\n\nConfirmation: \(entry.confirmationLink)" } event.notes = notes // Add an alert 1 hour before event.addAlarm(EKAlarm(relativeOffset: -60 * 60)) // Use default calendar event.calendar = eventStore.defaultCalendarForNewEvents do { try eventStore.save(event, span: .thisEvent) return .success(event.eventIdentifier) } catch { print("Error saving calendar event: \(error)") return .failure(.saveFailed(error.localizedDescription)) } } func removeEntryFromCalendar(eventIdentifier: String) -> Bool { guard let event = eventStore.event(withIdentifier: eventIdentifier) else { return false } do { try eventStore.remove(event, span: .thisEvent) return true } catch { print("Error removing calendar event: \(error)") return false } } // Check if an event already exists for this entry func eventExists(for entry: SpotEntry) -> Bool { let startOfDay = Calendar.current.startOfDay(for: entry.dateTime) let endOfDay = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay) ?? entry.dateTime let predicate = eventStore.predicateForEvents( withStart: startOfDay, end: endOfDay, calendars: nil ) let events = eventStore.events(matching: predicate) return events.contains { $0.title == entry.playName } } }`CalendarManager` classDefines the `CalendarManager` class. Conforms to ObservableObject.
▶ ERROR TYPES
enum CalendarError: LocalizedError { case accessDenied case saveFailed(String) var errorDescription: String? { switch self { case .accessDenied: return "Calendar access denied. Please enable calendar access in Settings." case .saveFailed(let message): return "Failed to save event: \(message)" } } }`CalendarError` enumDefines the `CalendarError` enum. Conforms to LocalizedError.