EventKit для Swift: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

EventKit для Swift: Полное руководство по работе с календарём и напоминаниями

EventKit — это мощный фреймворк от Apple, предоставляющий разработчикам Swift унифицированный интерфейс для взаимодействия с календарными базами данных и напоминаниями на устройствах iOS, iPadOS, macOS и watchOS. Он позволяет не только читать события и задачи, но и создавать, редактировать и удалять их, а также подписываться на уведомления об изменениях. Эта библиотека является ключевым инструментом для создания приложений-планировщиков, трекеров задач, корпоративных календарей и любых других программ, где требуется управление временем пользователя.

Зачем нужен EventKit? В современном мире управление временем — критически важная задача. EventKit даёт разработчику возможность интегрировать своё приложение с родными календарями и напоминаниями iOS/macOS, что избавляет пользователя от необходимости переключаться между разными программами. Вы можете автоматически добавлять встречи из вашего сервиса в календарь пользователя, синхронизировать дедлайны задач с напоминаниями или анализировать занятость. Без EventKit каждое приложение было бы изолированным "островом" данных, а с ним — становится частью единой экосистемы Apple.

Важно понимать, что EventKit — это не сторонняя библиотека, а системный фреймворк. Он встроен в SDK Apple и не требует установки через менеджеры пакетов для базового использования. Однако, для удобства работы и добавления дополнительных функций (например, реактивных обновлений) разработчики часто используют обёртки, такие как RxEventKit или EventKitUI. В этой статье мы сфокусируемся на чистом EventKit, так как это основа.

Установка (Подключение к проекту)

Поскольку EventKit является частью системного фреймворка Apple, его "установка" сводится к импорту модуля в ваш код. Команды pip или npm здесь не применимы. Для подключения выполните следующие шаги:

  1. Импорт в файле Swift: В начале вашего файла добавьте import EventKit.
  2. Добавление фреймворка в проект (если требуется): Обычно Xcode автоматически линкует системные фреймворки. Если нет — перейдите в Target > General > Frameworks, Libraries, and Embedded Content, нажмите "+" и добавьте EventKit.framework.
  3. Настройка Info.plist: Для доступа к календарю и напоминаниям необходимо добавить строки конфиденциальности:
    • NSCalendarsUsageDescription — строка, объясняющая, зачем приложению нужен доступ к календарю (например, "Чтобы добавлять встречи").
    • NSRemindersUsageDescription — строка для доступа к напоминаниям.
    Без этих строк приложение вызовет краш при попытке запроса доступа.
// Установка не требуется, просто импортируем фреймворк
import EventKit
import UIKit

class ViewController: UIViewController {
    // Ваш код
}

Быстрый старт: минимальный рабочий пример

Давайте создадим простое приложение, которое запрашивает доступ к календарю и создаёт одно событие "Тестовая встреча" на завтра.

import EventKit
import UIKit

class QuickStartViewController: UIViewController {

    let eventStore = EKEventStore()

    override func viewDidLoad() {
        super.viewDidLoad()
        requestAccessAndCreateEvent()
    }

    func requestAccessAndCreateEvent() {
        // 1. Запрашиваем доступ к календарю
        eventStore.requestAccess(to: .event) { [weak self] (granted, error) in
            guard let self = self else { return }
            
            if granted && error == nil {
                // 2. Создаём событие
                let event = EKEvent(eventStore: self.eventStore)
                event.title = "Тестовая встреча"
                event.startDate = Date().addingTimeInterval(24 * 60 * 60) // Завтра
                event.endDate = event.startDate.addingTimeInterval(60 * 60) // Через час
                event.calendar = self.eventStore.defaultCalendarForNewEvents
                
                do {
                    try self.eventStore.save(event, span: .thisEvent, commit: true)
                    print("Событие создано: \(event.eventIdentifier ?? "Нет ID")")
                } catch {
                    print("Ошибка сохранения: \(error.localizedDescription)")
                }
            } else {
                print("Доступ запрещён или ошибка: \(error?.localizedDescription ?? "Неизвестная ошибка")")
            }
        }
    }
}

Этот пример демонстрирует три ключевых шага: запрос разрешения, создание объекта EKEvent и его сохранение в календарь. Обратите внимание, что все операции с EventKit должны выполняться на фоновом потоке, чтобы не блокировать UI.

Основные классы и методы EventKit

Ниже представлен разбор 8 ключевых компонентов фреймворка. Каждый элемент включает сигнатуру, описание и рабочий пример.

1. EKEventStore — Хранилище событий

Сигнатура: class EKEventStore : NSObject

Описание: Основной класс для доступа к календарным базам данных. Через него выполняются все операции: чтение, запись, удаление событий и напоминаний.

Пример:

let eventStore = EKEventStore()
// Инициализация должна быть единственной на время жизни приложения (синглтон).
// Используйте один экземпляр для всех операций.

2. requestAccess(to:completion:) — Запрос доступа

Сигнатура: func requestAccess(to entityType: EKEntityType, completion: @escaping EKEventStoreRequestAccessCompletionHandler)

Описание: Запрашивает у пользователя разрешение на доступ к календарю (.event) или напоминаниям (.reminder). Вызывается один раз; последующие вызовы возвращают текущий статус.

Пример:

eventStore.requestAccess(to: .reminder) { granted, error in
    if granted {
        print("Доступ к напоминаниям разрешён")
    } else {
        // Покажите пользователю объяснение, почему нужен доступ
        print("Доступ запрещён: \(error?.localizedDescription ?? "")")
    }
}

3. EKEvent — Событие календаря

Сигнатура: class EKEvent : EKCalendarItem

Описание: Представляет собой одно событие в календаре. Содержит свойства: title, startDate, endDate, calendar, location, notes и другие.

Пример:

let newEvent = EKEvent(eventStore: eventStore)
newEvent.title = "Встреча с командой"
newEvent.startDate = Date()
newEvent.endDate = Date().addingTimeInterval(3600)
newEvent.notes = "Обсудить спринт"
newEvent.calendar = eventStore.defaultCalendarForNewEvents

4. save(_:span:commit:) — Сохранение события

Сигнатура: func save(_ event: EKEvent, span: EKSpan, commit: Bool) throws

Описание: Сохраняет новое или обновляет существующее событие. Параметр span определяет, применяются ли изменения к одному событию (.thisEvent) или ко всей серии повторяющихся событий (.futureEvents).

Пример:

do {
    try eventStore.save(newEvent, span: .thisEvent, commit: true)
    print("Событие сохранено с ID: \(newEvent.eventIdentifier ?? "нет")")
} catch {
    print("Ошибка сохранения: \(error)")
}

5. predicateForEvents(withStart:end:calendars:) — Поиск событий

Сигнатура: func predicateForEvents(withStart startDate: Date, end endDate: Date, calendars: [EKCalendar]?) -> NSPredicate

Описание: Создаёт предикат для поиска событий в заданном диапазоне дат. Можно ограничить поиск конкретными календарями.

Пример:

let startDate = Date()
let endDate = Date().addingTimeInterval(7 * 24 * 3600) // Неделя
let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
let events = eventStore.events(matching: predicate)
for event in events {
    print("Найдено событие: \(event.title) в \(event.startDate)")
}

6. EKReminder — Напоминание

Сигнатура: class EKReminder : EKCalendarItem

Описание: Представляет задачу или напоминание. В отличие от EKEvent, напоминание не привязано ко времени начала/окончания, но может иметь дату выполнения (dueDateComponents) и приоритет.

Пример:

let reminder = EKReminder(eventStore: eventStore)
reminder.title = "Купить молоко"
reminder.calendar = eventStore.defaultCalendarForNewReminders()
let dueDate = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: Date().addingTimeInterval(86400))
reminder.dueDateComponents = dueDate
reminder.priority = 1 // Высокий приоритет

7. fetchReminders(matching:completion:) — Получение напоминаний

Сигнатура: func fetchReminders(matching predicate: NSPredicate, completion: @escaping ([EKReminder]?) -> Void)

Описание: Асинхронно загружает напоминания, соответствующие предикату. Предикат создаётся методами predicateForReminders(in:) или predicateForIncompleteReminders(withDueDateStarting:ending:calendars:).

Пример:

let predicate = eventStore.predicateForIncompleteReminders(withDueDateStarting: nil, ending: Date(), calendars: nil)
eventStore.fetchReminders(matching: predicate) { reminders in
    guard let reminders = reminders else { return }
    for reminder in reminders {
        print("Просроченное напоминание: \(reminder.title)")
    }
}

8. remove(_:span:commit:) — Удаление событий/напоминаний

Сигнатура: func remove(_ event: EKEvent, span: EKSpan, commit: Bool) throws (для событий)
func remove(_ reminder: EKReminder, commit: Bool) throws (для напоминаний)

Описание: Удаляет указанное событие или напоминание из календаря. Для повторяющихся событий можно удалить только одно или все будущие.

Пример:

// Удаление первого найденного события
if let eventToDelete = events.first {
    do {
        try eventStore.remove(eventToDelete, span: .thisEvent, commit: true)
        print("Событие удалено")
    } catch {
        print("Ошибка удаления: \(error)")
    }
}

Реальный практический пример: Менеджер встреч и задач

Создадим класс CalendarManager, который объединяет работу с календарём и напоминаниями. Он будет уметь создавать встречи, добавлять к ним напоминания за 15 минут и выводить список всех событий на сегодня.

import EventKit
import Foundation

class CalendarManager {
    
    static let shared = CalendarManager()
    private let eventStore = EKEventStore()
    
    private init() {}
    
    // MARK: - Запрос доступа ко всему сразу
    func requestFullAccess(completion: @escaping (Bool) -> Void) {
        let group = DispatchGroup()
        var calendarGranted = false
        var reminderGranted = false
        
        group.enter()
        eventStore.requestAccess(to: .event) { granted, _ in
            calendarGranted = granted
            group.leave()
        }
        
        group.enter()
        eventStore.requestAccess(to: .reminder) { granted, _ in
            reminderGranted = granted
            group.leave()
        }
        
        group.notify(queue: .main) {
            completion(calendarGranted && reminderGranted)
        }
    }
    
    // MARK: - Создание встречи с напоминанием
    func createMeetingWithReminder(title: String, startDate: Date, durationInMinutes: Int, completion: @escaping (Bool) -> Void) {
        // 1. Создаём событие
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = startDate
        event.endDate = startDate.addingTimeInterval(TimeInterval(durationInMinutes * 60))
        event.calendar = eventStore.defaultCalendarForNewEvents
        
        // 2. Добавляем напоминание за 15 минут (алерт)
        let alarm = EKAlarm(relativeOffset: -15 * 60) // -900 секунд
        event.addAlarm(alarm)
        
        // 3. Сохраняем событие
        do {
            try eventStore.save(event, span: .thisEvent, commit: true)
            print("Встреча '\(title)' создана")
            
            // 4. Создаём связанное напоминание (задачу) в списке напоминаний
            let reminder = EKReminder(eventStore: eventStore)
            reminder.title = "Подготовиться к: \(title)"
            reminder.calendar = eventStore.defaultCalendarForNewReminders()
            
            // Устанавливаем дедлайн за 30 минут до встречи
            let deadline = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: startDate.addingTimeInterval(-30 * 60))
            reminder.dueDateComponents = deadline
            
            let reminderAlarm = EKAlarm(relativeOffset: -60 * 60) // За час до дедлайна
            reminder.addAlarm(reminderAlarm)
            
            try eventStore.save(rem                    

Рекомендаций