eventkit-calendar
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEventKit — Calendar & Reminders
EventKit — 日历与提醒事项
Create, read, and manage calendar events and reminders. Covers authorization,
event and reminder CRUD, recurrence rules, alarms, and EventKitUI editors.
Targets Swift 6.2 / iOS 26+.
创建、读取并管理日历事件与提醒事项。涵盖权限授权、事件与提醒的CRUD操作、重复规则、提醒闹钟以及EventKitUI编辑器。适配Swift 6.2 / iOS 26+。
Contents
目录
Setup
设置
Info.plist Keys
Info.plist 键
Add the required usage description strings based on what access level you need:
| Key | Access Level |
|---|---|
| Read + write events |
| Write-only events (iOS 17+) |
| Read + write reminders |
For apps also targeting iOS 16 or earlier, also include the legacy/NSCalendarsUsageDescriptionkeys.NSRemindersUsageDescription
根据所需的访问级别添加必要的使用描述字符串:
| 键 | 访问级别 |
|---|---|
| 读取+写入事件 |
| 仅写入事件(iOS 17+) |
| 读取+写入提醒事项 |
若应用同时适配iOS 16或更早版本,还需添加旧版的/NSCalendarsUsageDescription键。NSRemindersUsageDescription
Event Store
事件存储
Create a single instance and reuse it. Do not mix objects from
different event stores.
EKEventStoreswift
import EventKit
let eventStore = EKEventStore()创建单个实例并复用,不要混合使用来自不同事件存储的对象。
EKEventStoreswift
import EventKit
let eventStore = EKEventStore()Authorization
权限授权
iOS 17+ introduced granular access levels. Use the modern async methods.
iOS 17+引入了精细化的访问级别,请使用现代异步方法。
Full Access to Events
事件完全访问权限
swift
func requestCalendarAccess() async throws -> Bool {
let granted = try await eventStore.requestFullAccessToEvents()
return granted
}swift
func requestCalendarAccess() async throws -> Bool {
let granted = try await eventStore.requestFullAccessToEvents()
return granted
}Write-Only Access to Events
事件仅写入权限
Use when your app only creates events (e.g., saving a booking) and does not
need to read existing events.
swift
func requestWriteAccess() async throws -> Bool {
let granted = try await eventStore.requestWriteOnlyAccessToEvents()
return granted
}当应用仅需创建事件(例如保存预订信息)且无需读取现有事件时使用。
swift
func requestWriteAccess() async throws -> Bool {
let granted = try await eventStore.requestWriteOnlyAccessToEvents()
return granted
}Full Access to Reminders
提醒事项完全访问权限
swift
func requestRemindersAccess() async throws -> Bool {
let granted = try await eventStore.requestFullAccessToReminders()
return granted
}swift
func requestRemindersAccess() async throws -> Bool {
let granted = try await eventStore.requestFullAccessToReminders()
return granted
}Checking Authorization Status
检查授权状态
swift
let status = EKEventStore.authorizationStatus(for: .event)
switch status {
case .notDetermined:
// Request access
break
case .fullAccess:
// Read and write allowed
break
case .writeOnly:
// Write-only access granted (iOS 17+)
break
case .restricted:
// Parental controls or MDM restriction
break
case .denied:
// User denied -- direct to Settings
break
@unknown default:
break
}swift
let status = EKEventStore.authorizationStatus(for: .event)
switch status {
case .notDetermined:
// 请求权限
break
case .fullAccess:
// 允许读取和写入
break
case .writeOnly:
// 已授予仅写入权限(iOS 17+)
break
case .restricted:
// 受家长控制或MDM限制
break
case .denied:
// 用户拒绝 -- 引导至设置页面
break
@unknown default:
break
}Creating Events
创建事件
swift
func createEvent(
title: String,
startDate: Date,
endDate: Date,
calendar: EKCalendar? = nil
) throws {
let event = EKEvent(eventStore: eventStore)
event.title = title
event.startDate = startDate
event.endDate = endDate
event.calendar = calendar ?? eventStore.defaultCalendarForNewEvents
try eventStore.save(event, span: .thisEvent)
}swift
func createEvent(
title: String,
startDate: Date,
endDate: Date,
calendar: EKCalendar? = nil
) throws {
let event = EKEvent(eventStore: eventStore)
event.title = title
event.startDate = startDate
event.endDate = endDate
event.calendar = calendar ?? eventStore.defaultCalendarForNewEvents
try eventStore.save(event, span: .thisEvent)
}Setting a Specific Calendar
指定特定日历
swift
// List writable calendars
let calendars = eventStore.calendars(for: .event)
.filter { $0.allowsContentModifications }
// Use the first writable calendar, or the default
let targetCalendar = calendars.first ?? eventStore.defaultCalendarForNewEvents
event.calendar = targetCalendarswift
// 列出可写入的日历
let calendars = eventStore.calendars(for: .event)
.filter { $0.allowsContentModifications }
// 使用第一个可写入的日历,或默认日历
let targetCalendar = calendars.first ?? eventStore.defaultCalendarForNewEvents
event.calendar = targetCalendarAdding Structured Location
添加结构化位置
swift
import CoreLocation
let location = EKStructuredLocation(title: "Apple Park")
location.geoLocation = CLLocation(latitude: 37.3349, longitude: -122.0090)
event.structuredLocation = locationswift
import CoreLocation
let location = EKStructuredLocation(title: "Apple Park")
location.geoLocation = CLLocation(latitude: 37.3349, longitude: -122.0090)
event.structuredLocation = locationFetching Events
获取事件
Use a date-range predicate to query events. The method
returns occurrences of recurring events expanded within the range.
events(matching:)swift
func fetchEvents(from start: Date, to end: Date) -> [EKEvent] {
let predicate = eventStore.predicateForEvents(
withStart: start,
end: end,
calendars: nil // nil = all calendars
)
return eventStore.events(matching: predicate)
.sorted { $0.startDate < $1.startDate }
}使用日期范围谓词查询事件。方法会返回在该范围内展开的重复事件实例。
events(matching:)swift
func fetchEvents(from start: Date, to end: Date) -> [EKEvent] {
let predicate = eventStore.predicateForEvents(
withStart: start,
end: end,
calendars: nil // nil = 所有日历
)
return eventStore.events(matching: predicate)
.sorted { $0.startDate < $1.startDate }
}Fetching a Single Event by Identifier
通过标识符获取单个事件
swift
if let event = eventStore.event(withIdentifier: savedEventID) {
print(event.title ?? "No title")
}swift
if let event = eventStore.event(withIdentifier: savedEventID) {
print(event.title ?? "无标题")
}Reminders
提醒事项
Creating a Reminder
创建提醒事项
swift
func createReminder(title: String, dueDate: Date) throws {
let reminder = EKReminder(eventStore: eventStore)
reminder.title = title
reminder.calendar = eventStore.defaultCalendarForNewReminders()
let dueDateComponents = Calendar.current.dateComponents(
[.year, .month, .day, .hour, .minute],
from: dueDate
)
reminder.dueDateComponents = dueDateComponents
try eventStore.save(reminder, commit: true)
}swift
func createReminder(title: String, dueDate: Date) throws {
let reminder = EKReminder(eventStore: eventStore)
reminder.title = title
reminder.calendar = eventStore.defaultCalendarForNewReminders()
let dueDateComponents = Calendar.current.dateComponents(
[.year, .month, .day, .hour, .minute],
from: dueDate
)
reminder.dueDateComponents = dueDateComponents
try eventStore.save(reminder, commit: true)
}Fetching Reminders
获取提醒事项
Reminder fetches are asynchronous and return through a completion handler.
swift
func fetchIncompleteReminders() async -> [EKReminder] {
let predicate = eventStore.predicateForIncompleteReminders(
withDueDateStarting: nil,
ending: nil,
calendars: nil
)
return await withCheckedContinuation { continuation in
eventStore.fetchReminders(matching: predicate) { reminders in
continuation.resume(returning: reminders ?? [])
}
}
}提醒事项的获取是异步的,通过完成回调返回结果。
swift
func fetchIncompleteReminders() async -> [EKReminder] {
let predicate = eventStore.predicateForIncompleteReminders(
withDueDateStarting: nil,
ending: nil,
calendars: nil
)
return await withCheckedContinuation { continuation in
eventStore.fetchReminders(matching: predicate) { reminders in
continuation.resume(returning: reminders ?? [])
}
}
}Completing a Reminder
标记提醒事项为已完成
swift
func completeReminder(_ reminder: EKReminder) throws {
reminder.isCompleted = true
try eventStore.save(reminder, commit: true)
}swift
func completeReminder(_ reminder: EKReminder) throws {
reminder.isCompleted = true
try eventStore.save(reminder, commit: true)
}Recurrence Rules
重复规则
Use to create repeating events or reminders.
EKRecurrenceRule使用创建重复事件或提醒事项。
EKRecurrenceRuleSimple Recurrence
简单重复规则
swift
// Every week, indefinitely
let weeklyRule = EKRecurrenceRule(
recurrenceWith: .weekly,
interval: 1,
end: nil
)
event.addRecurrenceRule(weeklyRule)
// Every 2 weeks, ending after 10 occurrences
let biweeklyRule = EKRecurrenceRule(
recurrenceWith: .weekly,
interval: 2,
end: EKRecurrenceEnd(occurrenceCount: 10)
)
// Monthly, ending on a specific date
let monthlyRule = EKRecurrenceRule(
recurrenceWith: .monthly,
interval: 1,
end: EKRecurrenceEnd(end: endDate)
)swift
// 每周重复,无结束时间
let weeklyRule = EKRecurrenceRule(
recurrenceWith: .weekly,
interval: 1,
end: nil
)
event.addRecurrenceRule(weeklyRule)
// 每2周重复,重复10次后结束
let biweeklyRule = EKRecurrenceRule(
recurrenceWith: .weekly,
interval: 2,
end: EKRecurrenceEnd(occurrenceCount: 10)
)
// 每月重复,在指定日期结束
let monthlyRule = EKRecurrenceRule(
recurrenceWith: .monthly,
interval: 1,
end: EKRecurrenceEnd(end: endDate)
)Complex Recurrence
复杂重复规则
swift
// Every Monday and Wednesday
let days = [
EKRecurrenceDayOfWeek(.monday),
EKRecurrenceDayOfWeek(.wednesday)
]
let complexRule = EKRecurrenceRule(
recurrenceWith: .weekly,
interval: 1,
daysOfTheWeek: days,
daysOfTheMonth: nil,
monthsOfTheYear: nil,
weeksOfTheYear: nil,
daysOfTheYear: nil,
setPositions: nil,
end: nil
)
event.addRecurrenceRule(complexRule)swift
// 每周一和周三重复
let days = [
EKRecurrenceDayOfWeek(.monday),
EKRecurrenceDayOfWeek(.wednesday)
]
let complexRule = EKRecurrenceRule(
recurrenceWith: .weekly,
interval: 1,
daysOfTheWeek: days,
daysOfTheMonth: nil,
monthsOfTheYear: nil,
weeksOfTheYear: nil,
daysOfTheYear: nil,
setPositions: nil,
end: nil
)
event.addRecurrenceRule(complexRule)Editing Recurring Events
编辑重复事件
When saving changes to a recurring event, specify the span:
swift
// Change only this occurrence
try eventStore.save(event, span: .thisEvent)
// Change this and all future occurrences
try eventStore.save(event, span: .futureEvents)保存对重复事件的修改时,需指定范围:
swift
// 仅修改本次事件
try eventStore.save(event, span: .thisEvent)
// 修改本次及所有未来的事件
try eventStore.save(event, span: .futureEvents)Alarms
提醒闹钟
Attach alarms to events or reminders to trigger notifications.
swift
// 15 minutes before
let alarm = EKAlarm(relativeOffset: -15 * 60)
event.addAlarm(alarm)
// At an absolute date
let absoluteAlarm = EKAlarm(absoluteDate: alertDate)
event.addAlarm(absoluteAlarm)为事件或提醒事项添加闹钟以触发通知。
swift
// 提前15分钟
let alarm = EKAlarm(relativeOffset: -15 * 60)
event.addAlarm(alarm)
// 在指定绝对时间触发
let absoluteAlarm = EKAlarm(absoluteDate: alertDate)
event.addAlarm(absoluteAlarm)EventKitUI Controllers
EventKitUI控制器
EKEventEditViewController — Create/Edit Events
EKEventEditViewController — 创建/编辑事件
Present the system event editor for creating or editing events.
swift
import EventKitUI
class EventEditorCoordinator: NSObject, EKEventEditViewDelegate {
let eventStore = EKEventStore()
func presentEditor(from viewController: UIViewController) {
let editor = EKEventEditViewController()
editor.eventStore = eventStore
editor.editViewDelegate = self
viewController.present(editor, animated: true)
}
func eventEditViewController(
_ controller: EKEventEditViewController,
didCompleteWith action: EKEventEditViewAction
) {
switch action {
case .saved:
// Event saved
break
case .canceled:
break
case .deleted:
break
@unknown default:
break
}
controller.dismiss(animated: true)
}
}展示系统事件编辑器以创建或编辑事件。
swift
import EventKitUI
class EventEditorCoordinator: NSObject, EKEventEditViewDelegate {
let eventStore = EKEventStore()
func presentEditor(from viewController: UIViewController) {
let editor = EKEventEditViewController()
editor.eventStore = eventStore
editor.editViewDelegate = self
viewController.present(editor, animated: true)
}
func eventEditViewController(
_ controller: EKEventEditViewController,
didCompleteWith action: EKEventEditViewAction
) {
switch action {
case .saved:
// 事件已保存
break
case .canceled:
break
case .deleted:
break
@unknown default:
break
}
controller.dismiss(animated: true)
}
}EKEventViewController — View an Event
EKEventViewController — 查看事件
swift
import EventKitUI
let viewer = EKEventViewController()
viewer.event = existingEvent
viewer.allowsEditing = true
navigationController?.pushViewController(viewer, animated: true)swift
import EventKitUI
let viewer = EKEventViewController()
viewer.event = existingEvent
viewer.allowsEditing = true
navigationController?.pushViewController(viewer, animated: true)EKCalendarChooser — Select Calendars
EKCalendarChooser — 选择日历
swift
let chooser = EKCalendarChooser(
selectionStyle: .multiple,
displayStyle: .allCalendars,
entityType: .event,
eventStore: eventStore
)
chooser.showsDoneButton = true
chooser.showsCancelButton = true
chooser.delegate = self
present(UINavigationController(rootViewController: chooser), animated: true)swift
let chooser = EKCalendarChooser(
selectionStyle: .multiple,
displayStyle: .allCalendars,
entityType: .event,
eventStore: eventStore
)
chooser.showsDoneButton = true
chooser.showsCancelButton = true
chooser.delegate = self
present(UINavigationController(rootViewController: chooser), animated: true)Observing Changes
监听变更
Register for notifications to keep your UI in sync when
events are modified outside your app (e.g., by the Calendar app or a sync).
EKEventStoreChangedswift
NotificationCenter.default.addObserver(
forName: .EKEventStoreChanged,
object: eventStore,
queue: .main
) { [weak self] _ in
self?.refreshEvents()
}Always re-fetch events after receiving this notification. Previously fetched
objects may be stale.
EKEvent注册通知,以便在事件被外部应用(例如日历应用或同步服务)修改时保持UI同步。
EKEventStoreChangedswift
NotificationCenter.default.addObserver(
forName: .EKEventStoreChanged,
object: eventStore,
queue: .main
) { [weak self] _ in
self?.refreshEvents()
}收到该通知后,请务必重新获取事件。之前获取的对象可能已过期。
EKEventCommon Mistakes
常见错误
DON'T: Use the deprecated requestAccess(to:) method
不要:使用已废弃的requestAccess(to:)方法
swift
// WRONG: Deprecated in iOS 17
eventStore.requestAccess(to: .event) { granted, error in }
// CORRECT: Use the granular async methods
let granted = try await eventStore.requestFullAccessToEvents()swift
// 错误:在iOS 17中已废弃
eventStore.requestAccess(to: .event) { granted, error in }
// 正确:使用精细化的异步方法
let granted = try await eventStore.requestFullAccessToEvents()DON'T: Save events to a read-only calendar
不要:将事件保存到只读日历
swift
// WRONG: No check -- will throw if calendar is read-only
event.calendar = someCalendar
try eventStore.save(event, span: .thisEvent)
// CORRECT: Verify the calendar allows modifications
guard someCalendar.allowsContentModifications else {
event.calendar = eventStore.defaultCalendarForNewEvents
return
}
event.calendar = someCalendar
try eventStore.save(event, span: .thisEvent)swift
// 错误:未检查权限 -- 如果日历为只读会抛出异常
event.calendar = someCalendar
try eventStore.save(event, span: .thisEvent)
// 正确:验证日历是否允许修改
guard someCalendar.allowsContentModifications else {
event.calendar = eventStore.defaultCalendarForNewEvents
return
}
event.calendar = someCalendar
try eventStore.save(event, span: .thisEvent)DON'T: Ignore timezone when creating events
不要:创建事件时忽略时区
swift
// WRONG: Event appears at wrong time for traveling users
event.startDate = Date()
event.endDate = Date().addingTimeInterval(3600)
// CORRECT: Set the timezone explicitly for location-specific events
event.timeZone = TimeZone(identifier: "America/New_York")
event.startDate = startDate
event.endDate = endDateswift
// 错误:对于经常出差的用户,事件时间会显示错误
event.startDate = Date()
event.endDate = Date().addingTimeInterval(3600)
// 正确:为特定地点的事件显式设置时区
event.timeZone = TimeZone(identifier: "America/New_York")
event.startDate = startDate
event.endDate = endDateDON'T: Forget to commit batched saves
不要:批量保存后忘记提交
swift
// WRONG: Changes never persisted
try eventStore.save(event1, span: .thisEvent, commit: false)
try eventStore.save(event2, span: .thisEvent, commit: false)
// Missing commit!
// CORRECT: Commit after batching
try eventStore.save(event1, span: .thisEvent, commit: false)
try eventStore.save(event2, span: .thisEvent, commit: false)
try eventStore.commit()swift
// 错误:变更不会持久化
try eventStore.save(event1, span: .thisEvent, commit: false)
try eventStore.save(event2, span: .thisEvent, commit: false)
// 缺少提交操作!
// 正确:批量操作后提交
try eventStore.save(event1, span: .thisEvent, commit: false)
try eventStore.save(event2, span: .thisEvent, commit: false)
try eventStore.commit()DON'T: Mix EKObjects from different event stores
不要:混合使用来自不同事件存储的EKObjects
swift
// WRONG: Event fetched from storeA, saved to storeB
let event = storeA.event(withIdentifier: id)!
try storeB.save(event, span: .thisEvent) // Undefined behavior
// CORRECT: Use the same store throughout
let event = eventStore.event(withIdentifier: id)!
try eventStore.save(event, span: .thisEvent)swift
// 错误:从storeA获取的事件,保存到storeB
let event = storeA.event(withIdentifier: id)!
try storeB.save(event, span: .thisEvent) // 行为未定义
// 正确:全程使用同一个事件存储
let event = eventStore.event(withIdentifier: id)!
try eventStore.save(event, span: .thisEvent)Review Checklist
审核检查清单
- Correct usage description keys added for calendars and/or reminders
Info.plist - Authorization requested with iOS 17+ granular methods (,
requestFullAccessToEvents,requestWriteOnlyAccessToEvents)requestFullAccessToReminders - Authorization status checked before fetching or saving
- Single instance reused across the app
EKEventStore - Events saved to a writable calendar (checked)
allowsContentModifications - Recurring event saves specify correct (
EKSpanvs.thisEvent).futureEvents - Batched saves followed by explicit
commit() - notification observed to refresh stale data
EKEventStoreChanged - Timezone set explicitly for location-specific events
- EKObjects not shared across different event store instances
- EventKitUI delegates dismiss controllers in completion callbacks
- 已为日历和/或提醒事项添加正确的使用描述键
Info.plist - 使用iOS 17+的精细化方法请求授权(、
requestFullAccessToEvents、requestWriteOnlyAccessToEvents)requestFullAccessToReminders - 在获取或保存前检查授权状态
- 应用中复用单个实例
EKEventStore - 事件保存到可写入的日历(已检查)
allowsContentModifications - 保存重复事件时指定正确的(
EKSpan或.thisEvent).futureEvents - 批量保存后显式调用
commit() - 监听通知以刷新过期数据
EKEventStoreChanged - 为特定地点的事件显式设置时区
- 未在不同事件存储实例之间共享EKObjects
- EventKitUI代理在完成回调中关闭控制器
References
参考资料
- Extended patterns (SwiftUI wrappers, predicate queries, batch operations):
references/eventkit-patterns.md - EventKit framework
- EKEventStore
- EKEvent
- EKReminder
- EKRecurrenceRule
- EKCalendar
- EventKit UI
- EKEventEditViewController
- EKCalendarChooser
- Accessing the event store
- Creating a recurring event
- 扩展模式(SwiftUI封装、谓词查询、批量操作):
references/eventkit-patterns.md - EventKit框架
- EKEventStore
- EKEvent
- EKReminder
- EKRecurrenceRule
- EKCalendar
- EventKit UI
- EKEventEditViewController
- EKCalendarChooser
- 访问事件存储
- 创建重复事件