CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

eventkit-patterns.mdskills/eventkit/references/

EventKit Extended Patterns

Overflow reference for the eventkit skill. Contains advanced patterns that exceed the main skill file's scope.

Contents

  • SwiftUI Calendar Integration
  • Advanced Predicate Queries
  • Batch Operations
  • Calendar Management
  • Reminder Workflows
  • EventKitUI in SwiftUI
  • Typed Change Notifications

SwiftUI Calendar Integration

Prerequisite: pass the main skill's full-access authorization gate before using this read/write integration. Keep the iOS 17+ request and older-system fallback in that single authorization path.

Observable Event Manager

import EventKit
import SwiftUI

@Observable
@MainActor
final class CalendarManager {
    let eventStore = EKEventStore()
    var events: [EKEvent] = []
    var authorizationStatus: EKAuthorizationStatus = .notDetermined

    func requestAccess() async {
        do {
            let granted = try await eventStore.requestFullAccessToEvents()
            authorizationStatus = granted ? .fullAccess : .denied
            if granted { fetchThisWeekEvents() }
        } catch {
            authorizationStatus = .denied
        }
    }

    func fetchThisWeekEvents() {
        let calendar = Calendar.current
        let start = calendar.startOfDay(for: Date())
        let end = calendar.date(byAdding: .day, value: 7, to: start)!

        let predicate = eventStore.predicateForEvents(
            withStart: start,
            end: end,
            calendars: nil
        )
        events = eventStore.events(matching: predicate)
            .sorted { $0.startDate < $1.startDate }
    }

    func observeChanges() {
        NotificationCenter.default.addObserver(
            forName: .EKEventStoreChanged,
            object: eventStore,
            queue: .main
        ) { [weak self] _ in
            self?.fetchThisWeekEvents()
        }
    }
}

SwiftUI View with Calendar Events

struct CalendarEventsView: View {
    @State private var manager = CalendarManager()

    var body: some View {
        List(manager.events, id: \.eventIdentifier) { event in
            VStack(alignment: .leading) {
                Text(event.title)
                    .font(.headline)
                Text(event.startDate, style: .date)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
        .task {
            await manager.requestAccess()
            manager.observeChanges()
        }
        .overlay {
            if manager.events.isEmpty {
                ContentUnavailableView(
                    "No Events",
                    systemImage: "calendar",
                    description: Text("No events this week.")
                )
            }
        }
    }
}

Advanced Predicate Queries

Event predicates search at most a four-year span. events(matching:) and enumerateEvents(matching:using:) are synchronous and include only committed events, so commit batched changes before querying and move large reads off the main actor when needed.

Events in a Specific Calendar

func fetchEvents(in calendar: EKCalendar, range: DateInterval) -> [EKEvent] {
    let predicate = eventStore.predicateForEvents(
        withStart: range.start,
        end: range.end,
        calendars: [calendar]
    )
    return eventStore.events(matching: predicate)
}

Completed Reminders in a Date Range

func fetchCompletedReminders(from start: Date, to end: Date) async -> [EKReminder] {
    let predicate = eventStore.predicateForCompletedReminders(
        withCompletionDateStarting: start,
        ending: end,
        calendars: nil
    )

    return await withCheckedContinuation { continuation in
        eventStore.fetchReminders(matching: predicate) { reminders in
            continuation.resume(returning: reminders ?? [])
        }
    }
}

Enumerating Events Efficiently

For large date ranges, use enumerateEvents to process events one at a time without loading all into memory.

func processAllEvents(from start: Date, to end: Date) {
    let predicate = eventStore.predicateForEvents(
        withStart: start,
        end: end,
        calendars: nil
    )

    eventStore.enumerateEvents(matching: predicate) { event, stop in
        // Process each event
        if event.title.contains("Cancel") {
            stop.pointee = true // Stop enumeration early
        }
    }
}

Batch Operations

Creating Multiple Events Efficiently

Validate inputs and writable calendars first. Stage individual operations with commit: false, then call the throwing commit() once. On any staging or commit failure, call reset() to discard unsaved EventKit state and propagate the error. reset() disassociates and invalidates every existing object created or retrieved from that store, so discard all retained EKObject instances. Correct the known cause, then refetch with full access or reconstruct with write-only access before creating a new batch; do not blindly retry or require a read-back.

enum EventBatchError: Error {
    case noWritableCalendar
    case readOnlyCalendar
}

func createEvents(from entries: [(String, Date, Date)]) throws {
    guard let calendar = eventStore.defaultCalendarForNewEvents,
          calendar.allowsContentModifications else {
        throw EventBatchError.noWritableCalendar
    }

    do {
        for (title, start, end) in entries {
            let event = EKEvent(eventStore: eventStore)
            event.title = title
            event.startDate = start
            event.endDate = end
            event.calendar = calendar
            try eventStore.save(event, span: .thisEvent, commit: false)
        }
        try eventStore.commit()
    } catch {
        eventStore.reset()
        throw error
    }
}

Deleting Events in Bulk

func deleteEvents(_ events: [EKEvent]) throws {
    guard events.allSatisfy({ $0.calendar.allowsContentModifications }) else {
        throw EventBatchError.readOnlyCalendar
    }

    do {
        for event in events {
            try eventStore.remove(event, span: .thisEvent, commit: false)
        }
        try eventStore.commit()
    } catch {
        eventStore.reset()
        throw error
    }
}

Resetting Unsaved Changes

// Discard all uncommitted changes
eventStore.reset()

Calendar Management

Creating a Custom Calendar

func createCalendar(name: String, color: CGColor) throws -> EKCalendar {
    let calendar = EKCalendar(for: .event, eventStore: eventStore)
    calendar.title = name
    calendar.cgColor = color

    // Find a local source (iCloud, local, etc.)
    if let localSource = eventStore.sources.first(where: {
        $0.sourceType == .local
    }) {
        calendar.source = localSource
    } else if let defaultSource = eventStore.defaultCalendarForNewEvents?.source {
        calendar.source = defaultSource
    }

    try eventStore.saveCalendar(calendar, commit: true)
    return calendar
}

Listing Calendars by Type

// All event calendars
let eventCalendars = eventStore.calendars(for: .event)

// All reminder calendars
let reminderCalendars = eventStore.calendars(for: .reminder)

// Only writable calendars
let writableCalendars = eventCalendars.filter { $0.allowsContentModifications }

Reminder Workflows

Reminder with Location-Based Alarm

import CoreLocation
import EventKit

enum CalendarError: Error {
    case remindersAccessRequired
    case missingDefaultReminderList
}

func createLocationReminder(
    title: String,
    latitude: Double,
    longitude: Double
) throws {
    guard EKEventStore.authorizationStatus(for: .reminder) == .fullAccess else {
        throw CalendarError.remindersAccessRequired
    }
    guard let reminderCalendar = eventStore.defaultCalendarForNewReminders() else {
        throw CalendarError.missingDefaultReminderList
    }

    let reminder = EKReminder(eventStore: eventStore)
    reminder.title = title
    reminder.calendar = reminderCalendar

    let location = EKStructuredLocation(title: "Target Location")
    location.geoLocation = CLLocation(latitude: latitude, longitude: longitude)
    location.radius = 200 // meters

    let alarm = EKAlarm(relativeOffset: 0)
    alarm.structuredLocation = location
    alarm.proximity = .enter  // .enter or .leave
    reminder.addAlarm(alarm)

    try eventStore.save(reminder, commit: true)
}

If the app uses the person's current location while creating location-based reminders, add NSLocationWhenInUseUsageDescription and request Core Location authorization in the location layer.

Reminder with Priority

let reminder = EKReminder(eventStore: eventStore)
reminder.title = "Important Task"
reminder.priority = 1  // 1-4: High, 5: Medium, 6-9: Low, 0: None
reminder.calendar = eventStore.defaultCalendarForNewReminders()

EventKitUI in SwiftUI

UIViewControllerRepresentable for Event Editor

import SwiftUI
import EventKitUI

struct EventEditView: UIViewControllerRepresentable {
    let eventStore: EKEventStore
    @Binding var isPresented: Bool

    func makeUIViewController(context: Context) -> EKEventEditViewController {
        let editor = EKEventEditViewController()
        editor.eventStore = eventStore
        editor.editViewDelegate = context.coordinator
        return editor
    }

    func updateUIViewController(
        _ uiViewController: EKEventEditViewController,
        context: Context
    ) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, EKEventEditViewDelegate {
        let parent: EventEditView

        init(_ parent: EventEditView) {
            self.parent = parent
        }

        func eventEditViewController(
            _ controller: EKEventEditViewController,
            didCompleteWith action: EKEventEditViewAction
        ) {
            parent.isPresented = false
        }
    }
}

On iOS 17+, the editor can create events without app calendar authorization. It runs out of process, so the app cannot inspect the dismissed controller to learn what the person saved unless the app separately has full access and refetches.

Usage in SwiftUI

struct CalendarView: View {
    @State private var showEditor = false
    let eventStore = EKEventStore()

    var body: some View {
        Button("Add Event") { showEditor = true }
            .sheet(isPresented: $showEditor) {
                EventEditView(
                    eventStore: eventStore,
                    isPresented: $showEditor
                )
            }
    }
}

Typed Change Notifications

Use .EKEventStoreChanged for broad compatibility. On iOS 26+, the typed notification message is available when you want Foundation's message API:

if #available(iOS 26.0, *) {
    let observation = NotificationCenter.default.addObserver(
        of: eventStore,
        for: .changed
    ) { _ in
        fetchThisWeekEvents()
    }
}

skills

.mcp.json

README.md

tile.json