axiom-eventkit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

EventKit — Discipline

EventKit — 规范

Core Philosophy

核心原则

"Request the minimum access needed, and only when it's needed."
Mental model: EventKit has three access tiers. Most apps need only the first (no access + system UI). Requesting more than you need means more users deny your request, and more code to maintain.
"仅在需要时请求所需的最小权限。"
心智模型:EventKit有三个访问层级。大多数应用只需要第一个层级(无访问权限+系统UI)。请求超出需求的权限会导致更多用户拒绝请求,同时也需要维护更多代码。

When to Use This Skill

何时使用此技能

Use this skill when:
  • Adding events or reminders to the user's calendar
  • Choosing between EventKitUI, write-only, or full access
  • Requesting calendar or reminder permissions
  • Fetching, querying, or displaying existing events
  • Migrating from pre-iOS 17 permission APIs
  • Creating virtual conference extensions
  • Implementing Siri Event Suggestions for reservations
  • Debugging "access denied" or missing events
Do NOT use this skill for:
  • Contacts framework questions (use contacts)
  • General SwiftUI architecture (use swiftui-architecture)
  • Background task scheduling (use background-processing)
在以下场景使用此技能:
  • 向用户日历添加事件或提醒事项
  • 在EventKitUI、仅写入或完全访问权限之间做选择
  • 请求日历或提醒事项权限
  • 获取、查询或显示现有事件
  • 从iOS 17之前的权限API迁移
  • 创建虚拟会议扩展
  • 为预订功能实现Siri事件建议
  • 调试“访问被拒绝”或事件丢失问题
请勿在以下场景使用此技能:
  • 联系人框架相关问题(请使用contacts技能)
  • 通用SwiftUI架构问题(请使用swiftui-architecture技能)
  • 后台任务调度问题(请使用background-processing技能)

Related Skills

相关技能

  • eventkit-ref — Complete EventKit/EventKitUI API reference
  • contacts — Contacts framework discipline skill
  • privacy-ux — General iOS privacy patterns and Permission UX
  • extensions-widgets — WidgetKit if combining calendar with widgets
  • background-processing — If scheduling background calendar sync

  • eventkit-ref — 完整的EventKit/EventKitUI API参考
  • contacts — 联系人框架规范技能
  • privacy-ux — 通用iOS隐私模式与权限UX
  • extensions-widgets — 若将日历与小组件结合使用,请使用WidgetKit
  • background-processing — 若调度后台日历同步

Access Tier Decision Tree

访问层级决策树

dot
digraph access_decision {
    rankdir=TB;
    "What does your app need?" [shape=diamond];
    "Add single events to Calendar?" [shape=diamond];
    "Show custom create/edit UI?" [shape=diamond];
    "Read existing events/calendars?" [shape=diamond];

    "No access + EventKitUI" [shape=box, label="Tier 1: No Access\nPresent EKEventEditViewController\nNo permission prompt needed"];
    "No access + Siri Suggestions" [shape=box, label="Tier 1: No Access\nSiri Event Suggestions\nFor reservations only"];
    "Write-only access" [shape=box, label="Tier 2: Write-Only\nrequestWriteOnlyAccessToEvents()\nCan save but not read"];
    "Full access" [shape=box, label="Tier 3: Full Access\nrequestFullAccessToEvents()\nor requestFullAccessToReminders()"];

    "What does your app need?" -> "Add single events to Calendar?" [label="events"];
    "What does your app need?" -> "Full access" [label="reminders\n(always full)"];
    "Add single events to Calendar?" -> "No access + EventKitUI" [label="yes, one at a time"];
    "Add single events to Calendar?" -> "Show custom create/edit UI?" [label="no, batch or silent"];
    "Show custom create/edit UI?" -> "Write-only access" [label="yes, or batch save"];
    "Show custom create/edit UI?" -> "Read existing events/calendars?" [label="no"];
    "Read existing events/calendars?" -> "Full access" [label="yes"];
    "Read existing events/calendars?" -> "Write-only access" [label="no"];
    "Add single events to Calendar?" -> "No access + Siri Suggestions" [label="reservation-style\n(restaurant, flight, hotel)"];
}
Key rule: Reminders ALWAYS require full access. There is no write-only tier for reminders.

dot
digraph access_decision {
    rankdir=TB;
    "What does your app need?" [shape=diamond];
    "Add single events to Calendar?" [shape=diamond];
    "Show custom create/edit UI?" [shape=diamond];
    "Read existing events/calendars?" [shape=diamond];

    "No access + EventKitUI" [shape=box, label="Tier 1: No Access\nPresent EKEventEditViewController\nNo permission prompt needed"];
    "No access + Siri Suggestions" [shape=box, label="Tier 1: No Access\nSiri Event Suggestions\nFor reservations only"];
    "Write-only access" [shape=box, label="Tier 2: Write-Only\nrequestWriteOnlyAccessToEvents()\nCan save but not read"];
    "Full access" [shape=box, label="Tier 3: Full Access\nrequestFullAccessToEvents()\nor requestFullAccessToReminders()"];

    "What does your app need?" -> "Add single events to Calendar?" [label="events"];
    "What does your app need?" -> "Full access" [label="reminders\n(always full)"];
    "Add single events to Calendar?" -> "No access + EventKitUI" [label="yes, one at a time"];
    "Add single events to Calendar?" -> "Show custom create/edit UI?" [label="no, batch or silent"];
    "Show custom create/edit UI?" -> "Write-only access" [label="yes, or batch save"];
    "Show custom create/edit UI?" -> "Read existing events/calendars?" [label="no"];
    "Read existing events/calendars?" -> "Full access" [label="yes"];
    "Read existing events/calendars?" -> "Write-only access" [label="no"];
    "Add single events to Calendar?" -> "No access + Siri Suggestions" [label="reservation-style\n(restaurant, flight, hotel)"];
}
关键规则:提醒事项始终需要完全访问权限。提醒事项没有仅写入的层级。

The Three Access Tiers

三个访问层级

Tier 1: No Access (Preferred)

层级1:无访问权限(推荐)

Present
EKEventEditViewController
— it runs out-of-process on iOS 17+ and requires zero permissions.
swift
let store = EKEventStore()
let event = EKEvent(eventStore: store)
event.title = "Team Standup"
event.startDate = startDate
event.endDate = Calendar.current.date(byAdding: .hour, value: 1, to: startDate) ?? startDate
event.timeZone = TimeZone(identifier: "America/Los_Angeles")
event.location = "Conference Room A"

let editVC = EKEventEditViewController()
editVC.event = event
editVC.eventStore = store
editVC.editViewDelegate = self
present(editVC, animated: true)
Why this is best: No permission prompt. No denial risk. System handles Calendar selection and save. Works on iOS 4+.
For reservations (restaurant, flight, hotel, event tickets), use Siri Event Suggestions instead — events appear in Calendar inbox without any permission. See the eventkit-ref skill for the INReservation donation pattern.
展示
EKEventEditViewController
—— 在iOS 17及以上版本中它是进程外运行的,不需要任何权限。
swift
let store = EKEventStore()
let event = EKEvent(eventStore: store)
event.title = "Team Standup"
event.startDate = startDate
event.endDate = Calendar.current.date(byAdding: .hour, value: 1, to: startDate) ?? startDate
event.timeZone = TimeZone(identifier: "America/Los_Angeles")
event.location = "Conference Room A"

let editVC = EKEventEditViewController()
editVC.event = event
editVC.eventStore = store
editVC.editViewDelegate = self
present(editVC, animated: true)
为何推荐:无权限提示,无被拒绝风险。系统处理日历选择和保存。支持iOS 4及以上版本。
对于预订类场景(餐厅、航班、酒店、活动门票),请使用Siri事件建议替代 —— 事件会出现在日历收件箱中,无需任何权限。请查看eventkit-ref技能中的INReservation捐赠模式。

Tier 2: Write-Only Access (iOS 17+)

层级2:仅写入权限(iOS 17+)

Use only when you need: custom editing UI, batch saves, or silent event creation.
swift
let store = EKEventStore()
guard try await store.requestWriteOnlyAccessToEvents() else {
    // User denied — handle gracefully
    return
}
let event = EKEvent(eventStore: store)
event.calendar = store.defaultCalendarForNewEvents  // REQUIRED for write-only
event.title = "Recurring Standup"
event.startDate = startDate
event.endDate = endDate
try store.save(event, span: .thisEvent)
Write-only constraints:
  • Returns a single virtual calendar, not the user's real calendars
  • Event queries return empty results
  • System chooses destination calendar for created events
  • Cannot read events back, even ones your app created
Info.plist required:
NSCalendarsWriteOnlyAccessUsageDescription
仅在需要以下功能时使用:自定义编辑UI、批量保存或静默创建事件。
swift
let store = EKEventStore()
guard try await store.requestWriteOnlyAccessToEvents() else {
    // 用户拒绝 —— 优雅处理
    return
}
let event = EKEvent(eventStore: store)
event.calendar = store.defaultCalendarForNewEvents  // 仅写入权限下必填
event.title = "Recurring Standup"
event.startDate = startDate
event.endDate = endDate
try store.save(event, span: .thisEvent)
仅写入权限限制
  • 返回单个虚拟日历,而非用户的真实日历
  • 事件查询返回空结果
  • 系统为创建的事件选择目标日历
  • 无法读取事件,即使是您的应用创建的事件
必填Info.plist键
NSCalendarsWriteOnlyAccessUsageDescription

Tier 3: Full Access

层级3:完全访问权限

Use only when your app's core feature requires reading, modifying, or deleting existing events.
swift
let store = EKEventStore()
guard try await store.requestFullAccessToEvents() else { return }

// Now you can fetch events
let interval = Calendar.current.dateInterval(of: .month, for: Date())!
let predicate = store.predicateForEvents(withStart: interval.start, end: interval.end, calendars: nil)
let events = store.events(matching: predicate)
    .sorted { $0.compareStartDate(with: $1) == .orderedAscending }
Info.plist required:
NSCalendarsFullAccessUsageDescription
For reminders:
swift
guard try await store.requestFullAccessToReminders() else { return }
Info.plist required:
NSRemindersFullAccessUsageDescription

仅当您的应用核心功能需要读取、修改或删除现有事件时使用。
swift
let store = EKEventStore()
guard try await store.requestFullAccessToEvents() else { return }

// 现在您可以获取事件
let interval = Calendar.current.dateInterval(of: .month, for: Date())!
let predicate = store.predicateForEvents(withStart: interval.start, end: interval.end, calendars: nil)
let events = store.events(matching: predicate)
    .sorted { $0.compareStartDate(with: $1) == .orderedAscending }
必填Info.plist键
NSCalendarsFullAccessUsageDescription
对于提醒事项:
swift
guard try await store.requestFullAccessToReminders() else { return }
必填Info.plist键
NSRemindersFullAccessUsageDescription

Anti-Patterns

反模式

PatternTime CostWhy It's WrongFix
Requesting full access for "add to calendar"1-2 sprint days recovering denied usersFull access prompts are denied 30%+ of the time — users distrust reading ALL calendar dataUse EventKitUI or write-only
Missing Info.plist key on iOS 17+1-2 hours debuggingAutomatic silent denial, no crash, no error, no promptAdd the correct usage description key
Missing Info.plist key on iOS 16 and belowImmediate crashApp crashes on permission requestAdd
NSCalendarsUsageDescription
Calling deprecated
requestAccess(to:)
on iOS 17
Throws errorThe old API throws, does not promptUse
requestFullAccessToEvents()
or
requestWriteOnlyAccessToEvents()
Creating multiple EKEventStore instancesStale data bugsObjects from one store cannot be used with anotherCreate one store, reuse it
Using
Date
math instead of
DateComponents
for durations
DST bugsAdding 3600 seconds doesn't always equal 1 hourUse
Calendar.current.date(byAdding:)
Not sorting
events(matching:)
results
Wrong display orderResults are NOT chronologically orderedSort with
compareStartDate(with:)
Setting
dueDateComponents
with
Date
instead of
DateComponents
Silent failureReminders use
DateComponents
, not
Date
Convert via
Calendar.current.dateComponents(...)
Not registering for
EKEventStoreChanged
notification
Stale UIExternal Calendar changes are invisibleRegister and refetch on notification
Ignoring
EKSpan
on recurring events
Modifying all occurrences
.thisEvent
vs
.futureEvents
controls scope
Always choose explicitly

模式时间成本错误原因修复方案
为“添加到日历”请求完全访问权限1-2个冲刺日用于挽回被拒绝的用户完全访问权限提示的拒绝率超过30% —— 用户不信任读取所有日历数据的请求使用EventKitUI或仅写入权限
iOS 17+版本缺少Info.plist键1-2小时调试时间自动静默拒绝,无崩溃、无错误、无提示添加正确的使用描述键
iOS 16及以下版本缺少Info.plist键立即崩溃应用在请求权限时崩溃添加
NSCalendarsUsageDescription
在iOS 17上调用已弃用的
requestAccess(to:)
抛出错误旧API会抛出错误,不会弹出提示使用
requestFullAccessToEvents()
requestWriteOnlyAccessToEvents()
创建多个EKEventStore实例数据过时bug一个存储实例的对象无法与另一个存储实例一起使用创建一个存储实例并复用
使用
Date
计算而非
DateComponents
处理时长
夏令时bug添加3600秒并不总是等于1小时使用
Calendar.current.date(byAdding:)
不对
events(matching:)
结果排序
显示顺序错误结果并非按时间顺序排列使用
compareStartDate(with:)
排序
使用
Date
而非
DateComponents
设置
dueDateComponents
静默失败提醒事项使用
DateComponents
而非
Date
通过
Calendar.current.dateComponents(...)
转换
未注册
EKEventStoreChanged
通知
UI数据过时外部日历的变更无法被感知注册通知并在收到时重新获取数据
处理重复事件时忽略
EKSpan
修改所有重复实例
.thisEvent
.futureEvents
控制修改范围
始终显式选择

Reminder Patterns

提醒事项模式

Reminders ALWAYS require
requestFullAccessToReminders()
.
提醒事项始终需要
requestFullAccessToReminders()

Creating a Reminder

创建提醒事项

swift
let reminder = EKReminder(eventStore: store)
reminder.title = "Review PR"
reminder.calendar = store.defaultCalendarForNewReminders()  // Required

// Due dates use DateComponents, NOT Date
if let dueDate = dueDate {
    reminder.dueDateComponents = Calendar.current.dateComponents(
        [.year, .month, .day, .hour, .minute], from: dueDate
    )
}

reminder.priority = EKReminderPriority.medium.rawValue
try store.save(reminder, commit: true)
swift
let reminder = EKReminder(eventStore: store)
reminder.title = "Review PR"
reminder.calendar = store.defaultCalendarForNewReminders()  // 必填

// 截止日期使用DateComponents,而非Date
if let dueDate = dueDate {
    reminder.dueDateComponents = Calendar.current.dateComponents(
        [.year, .month, .day, .hour, .minute], from: dueDate
    )
}

reminder.priority = EKReminderPriority.medium.rawValue
try store.save(reminder, commit: true)

Fetching Reminders (Async)

获取提醒事项(异步)

Unlike events, reminder fetches are asynchronous:
swift
let predicate = store.predicateForReminders(in: nil)  // nil = all calendars
let reminders = try await withCheckedThrowingContinuation { continuation in
    store.fetchReminders(matching: predicate) { reminders in
        if let reminders {
            continuation.resume(returning: reminders)
        } else {
            continuation.resume(throwing: TodayError.failedReadingReminders)
        }
    }
}
与事件不同,提醒事项的获取是异步的:
swift
let predicate = store.predicateForReminders(in: nil)  // nil = 所有日历
let reminders = try await withCheckedThrowingContinuation { continuation in
    store.fetchReminders(matching: predicate) { reminders in
        if let reminders {
            continuation.resume(returning: reminders)
        } else {
            continuation.resume(throwing: TodayError.failedReadingReminders)
        }
    }
}

Creating Reminder Lists

创建提醒事项列表

Reminder lists are
EKCalendar
objects filtered by entity type:
swift
let newList = EKCalendar(for: .reminder, eventStore: store)
newList.title = "Sprint Tasks"

// Source selection matters — prefer .local or .calDAV
guard let source = store.sources.first(where: {
    $0.sourceType == .local || $0.sourceType == .calDAV
}) ?? store.defaultCalendarForNewReminders()?.source else {
    throw EventKitError.noValidSource
}

newList.source = source
try store.saveCalendar(newList, commit: true)

提醒事项列表是按实体类型过滤的
EKCalendar
对象:
swift
let newList = EKCalendar(for: .reminder, eventStore: store)
newList.title = "Sprint Tasks"

// 源选择很重要 —— 优先选择.local或.calDAV
guard let source = store.sources.first(where: {
    $0.sourceType == .local || $0.sourceType == .calDAV
}) ?? store.defaultCalendarForNewReminders()?.source else {
    throw EventKitError.noValidSource
}

newList.source = source
try store.saveCalendar(newList, commit: true)

Store Lifecycle

存储生命周期

Singleton Pattern

单例模式

Create one
EKEventStore
and reuse it. Objects from one store instance cannot be used with another.
创建一个
EKEventStore
并复用。一个存储实例的对象无法与另一个存储实例一起使用。

Change Notifications

变更通知

swift
NotificationCenter.default.addObserver(
    self, selector: #selector(storeChanged),
    name: .EKEventStoreChanged, object: store
)

@objc func storeChanged(_ notification: Notification) {
    // Refetch your current date range
    // Individual objects: call refresh() — if false, refetch
}
swift
NotificationCenter.default.addObserver(
    self, selector: #selector(storeChanged),
    name: .EKEventStoreChanged, object: store
)

@objc func storeChanged(_ notification: Notification) {
    // 重新获取当前日期范围的数据
    // 单个对象:调用refresh() —— 如果返回false则重新获取
}

Batch Operations

批量操作

swift
// Pass commit: false for batch, then commit once
try store.save(event1, span: .thisEvent, commit: false)
try store.save(event2, span: .thisEvent, commit: false)
try store.commit()  // Atomic save
// On failure: store.reset() to rollback

swift
// 批量操作时传入commit: false,然后一次性提交
try store.save(event1, span: .thisEvent, commit: false)
try store.save(event2, span: .thisEvent, commit: false)
try store.commit()  // 原子性保存
// 失败时:调用store.reset()回滚

Migration from Pre-iOS 17

从iOS 17之前版本迁移

Before iOS 17iOS 17+ Replacement
requestAccess(to: .event)
requestFullAccessToEvents()
or
requestWriteOnlyAccessToEvents()
requestAccess(to: .reminder)
requestFullAccessToReminders()
NSCalendarsUsageDescription
NSCalendarsFullAccessUsageDescription
or
NSCalendarsWriteOnlyAccessUsageDescription
NSRemindersUsageDescription
NSRemindersFullAccessUsageDescription
authorizationStatus == .authorized
Check for
.fullAccess
or
.writeOnly
Runtime compatibility:
swift
if #available(iOS 17.0, *) {
    granted = try await store.requestFullAccessToEvents()
} else {
    granted = try await store.requestAccess(to: .event)
}
Keep old Info.plist keys alongside new ones to support iOS 16 and below.
Gotcha: Apps built with older Xcode SDKs map both
.writeOnly
and
.fullAccess
to
.authorized
. This means an app linked against an old SDK may fail to fetch events even after users granted full access — because the app sees
.authorized
but the system gave
.writeOnly
.

iOS 17之前iOS 17+替代方案
requestAccess(to: .event)
requestFullAccessToEvents()
requestWriteOnlyAccessToEvents()
requestAccess(to: .reminder)
requestFullAccessToReminders()
NSCalendarsUsageDescription
NSCalendarsFullAccessUsageDescription
NSCalendarsWriteOnlyAccessUsageDescription
NSRemindersUsageDescription
NSRemindersFullAccessUsageDescription
authorizationStatus == .authorized
检查
.fullAccess
.writeOnly
运行时兼容性
swift
if #available(iOS 17.0, *) {
    granted = try await store.requestFullAccessToEvents()
} else {
    granted = try await store.requestAccess(to: .event)
}
保留旧的Info.plist键,同时添加新键以支持iOS 16及以下版本。
注意事项:使用旧版Xcode SDK构建的应用会将
.writeOnly
.fullAccess
都映射为
.authorized
。这意味着链接到旧SDK的应用即使在用户授予完全访问权限后也可能无法获取事件 —— 因为应用看到的是
.authorized
,但系统授予的是
.writeOnly

EventKitUI Decision Guide

EventKitUI决策指南

ControllerPurposePermission Required
EKEventEditViewController
Create/edit eventsNone (iOS 17+ out-of-process)
EKEventViewController
Display event detailsFull access
EKCalendarChooser
Calendar selectionWrite-only or full
Gotcha:
EKEventEditViewController
inherits from
UINavigationController
, not
UIViewController
. Do NOT embed it inside another navigation controller.
Gotcha:
EKEventViewController
inherits from
UIViewController
and CAN be pushed onto a navigation stack.
Gotcha: Under write-only access,
EKCalendarChooser
ignores
displayStyle
and always shows writable calendars only.

控制器用途所需权限
EKEventEditViewController
创建/编辑事件无(iOS 17+进程外运行)
EKEventViewController
显示事件详情完全访问权限
EKCalendarChooser
日历选择仅写入或完全访问权限
注意事项
EKEventEditViewController
继承自
UINavigationController
,而非
UIViewController
。请勿将其嵌入另一个导航控制器中。
注意事项
EKEventViewController
继承自
UIViewController
,可以推入导航栈。
注意事项:在仅写入权限下,
EKCalendarChooser
会忽略
displayStyle
,始终仅显示可写入的日历。

Pressure Scenarios

压力场景

Scenario 1: "Just request full access, we might need it later"

场景1:“直接请求完全访问权限,我们以后可能需要”

Pressure: Product manager asks for full access "just in case."
Why resist: Full access prompts are denied 30%+ of the time. Write-only or EventKitUI gets you event creation with near-zero denials. You can always upgrade later if a reading feature is added.
Response: "Full access shows a scary prompt about reading ALL calendar data. For adding events, EventKitUI needs no prompt at all. Let's start there and upgrade if we ship a feature that reads events."
压力:产品经理要求“以防万一”请求完全访问权限。
为何拒绝:完全访问权限提示的拒绝率超过30%。仅写入权限或EventKitUI可以让您在几乎无拒绝率的情况下实现事件创建。如果以后添加读取功能,您可以随时升级权限。
回应:“完全访问权限会显示一个读取所有日历数据的可怕提示。对于添加事件,EventKitUI完全不需要权限提示。我们先从这个方案开始,如果以后发布读取事件的功能再升级权限。”

Scenario 2: "The deprecated API still works, we'll migrate later"

场景2:“已弃用的API还能用,我们以后再迁移”

Pressure: Deadline pressure to skip migration from
requestAccess(to:)
.
Why resist: On iOS 17, calling
requestAccess(to: .event)
throws an error — no prompt, no access, broken feature. Users on iOS 17+ get a silent failure.
Response: "The deprecated API throws on iOS 17. It's not 'deprecated but works' — it's broken. The fix is a 3-line
#available
check."
压力:截止日期压力要求跳过从
requestAccess(to:)
的迁移。
为何拒绝:在iOS 17上,调用
requestAccess(to: .event)
会抛出错误 —— 无提示、无访问权限、功能损坏。iOS 17+用户会遇到静默失败。
回应:“已弃用的API在iOS 17上会抛出错误。它不是‘已弃用但可用’ —— 它已经损坏。修复只需要3行
#available
检查代码。”

Scenario 3: "Just create a new EKEventStore for each screen"

场景3:“每个屏幕都创建一个新的EKEventStore”

Pressure: Different view controllers each create their own store for isolation.
Why resist: Objects from one store cannot be used with another. Events fetched from store A cannot be saved by store B. Change notifications only fire on the store that's registered.
Response: "EventKit requires a single shared store. Objects are bound to the store that created them. Create one and inject it."

压力:不同的视图控制器各自创建自己的存储实例以实现隔离。
为何拒绝:一个存储实例的对象无法与另一个存储实例一起使用。从存储A获取的事件无法被存储B保存。变更通知只会在注册的存储实例上触发。
回应:“EventKit要求使用单个共享存储实例。对象与创建它们的存储实例绑定。创建一个实例并注入到需要的地方。”

Error Handling

错误处理

Key
EKErrorDomain
codes to handle:
CodeMeaningFix
eventStoreNotAuthorized
No permissionCheck and request access first
noCalendar
Calendar not set on eventSet
event.calendar
before save
noStartDate
/
noEndDate
Missing datesSet both before save
datesInverted
End before startValidate date order
calendarReadOnly
/
calendarIsImmutable
Can't write to this calendarUse
allowsContentModifications
check
objectBelongsToDifferentStore
Cross-store usageUse single store instance
recurringReminderRequiresDueDate
Recurring reminder missing due dateSet
dueDateComponents

需要处理的关键
EKErrorDomain
错误码:
错误码含义修复方案
eventStoreNotAuthorized
无权限先检查并请求权限
noCalendar
事件未设置日历保存前设置
event.calendar
noStartDate
/
noEndDate
缺少日期保存前设置开始和结束日期
datesInverted
结束日期早于开始日期验证日期顺序
calendarReadOnly
/
calendarIsImmutable
无法写入此日历使用
allowsContentModifications
检查
objectBelongsToDifferentStore
跨存储实例使用对象使用单个存储实例
recurringReminderRequiresDueDate
重复提醒事项缺少截止日期设置
dueDateComponents

Resources

资源

WWDC: 2023-10052, 2020-10197
Docs: /eventkit, /eventkitui, /technotes/tn3152, /technotes/tn3153
Skills: eventkit-ref, contacts, privacy-ux, extensions-widgets
WWDC:2023-10052, 2020-10197
文档:/eventkit, /eventkitui, /technotes/tn3152, /technotes/tn3153
技能:eventkit-ref, contacts, privacy-ux, extensions-widgets