
使用 expo-calendar 接入系统日历事件、提醒与权限配置完整指南【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expoexpo-calendar是 Expo 生态中用于交互设备「系统日历」的原生模块。它提供了一套统一的 TypeScript API让 React Native 应用能够读写 Android/iOS 系统日历中的日历Calendar、事件Event、提醒Reminder以及相关的参与者Attendee、闹钟Alarm等记录。在本文对应仓库中该模块源码位于 packages/expo-calendar当前版本为57.0.1见 package.json。读完本文你将掌握如何在托管与裸工程中安装并配置双端权限、如何用新一代对象式 API 查询与创建日历和事件、如何处理 iOS 17 的只写write-only访问模式、以及如何从旧版方法迁移到新 API。一、expo-calendar 能做什么从模块的描述与导出看expo-calendar 围绕四类实体展开操作日历Calendar系统 Calendar 或 Reminders 应用中的一份日历包含标题、颜色、来源账号、允许的可用性类型等属性事件Event挂在某个日历下的日程记录支持单次与重复事件、全天与非全天、地理位置与附件 URL 等提醒ReminderiOS 专属的待办条目可设置截止日期、完成状态与完成时间关联记录参与者Attendee、闹钟Alarm以及事件重复规则RecurrenceRule。其中事件与提醒在 iOS 上对应Calendar 应用与Reminders 应用通过EntityTypes.EVENT/EntityTypes.REMINDER区分Android 上提醒能力由系统日历协议差异决定、不可用。这一点在源码中体现得很明显所有 Reminder 相关方法都会做平台守卫例如 src/Calendar.ts 中ExpoCalendarReminder的update、delete、get均在非 iOS 平台抛出UnavailabilityErrorgetSourcesSync、getDefaultCalendarSync、presentPicker等同理仅限 iOS。二、安装托管工程与裸工程两种方式模块官方 READMEpackages/expo-calendar/README.md区分了两种工程形态。托管managedExpo 工程直接用 Expo CLI 安装版本由 CLI 自动匹配当前 SDK 对应的原生模块版本npx expo install expo-calendar裸bareReact Native 工程前提是先完成 expo 模块基础设施的安装与配置然后执行同样的安装命令再补上双端原生配置npx expo install expo-calendar安装完成后若使用 CocoaPods需要在 iOS 目录执行npx pod-install从 package.json 可以看到该包通过exports提供了多个子路径入口.新版主入口、./next新 API 显式别名、./legacy旧版方法集、./plugin配置插件以及./app.plugin。其中next与主入口实际指向同一份新代码src/next/index.ts 与 src/index.ts 均export * from ./Calendar这样做是为了兼顾旧版expo-calendar/next导入习惯并平滑过渡。三、Android 与 iOS 原生权限配置访问系统日历属于敏感权限必须在原生层声明并经过用户授权。3.1 AndroidManifest 权限README 要求为 Android 手动在android/app/src/main/AndroidManifest.xml中添加读写两个权限uses-permission android:nameandroid.permission.READ_CALENDAR / uses-permission android:nameandroid.permission.WRITE_CALENDAR /其中READ_CALENDAR用于读取日历、事件与参会人WRITE_CALENDAR用于创建/修改事件与日历。该模块自身的 android/src/main/AndroidManifest.xml 不声明上述权限由宿主 App 注入但额外声明了一条queries当 targetSdk 为 30 及以上时调用系统事件页ACTION_VIEWcontentscheme打开日历事件前必须先声明该查询意图否则无法解析系统日历应用。提示Android 没有 iOS 那样的「默认日历」概念。源码中getDefaultCalendarSync()在 Android 上直接抛出UnavailabilityError见 src/Calendar.ts应改用getCalendars()并依据isPrimary字段挑选一个可写的账号主日历。3.2 iOSInfo.plist 描述文案README 给出的基础配置是在Info.plist中声明两条使用说明keyNSCalendarsUsageDescription/key stringAllow $(PRODUCT_NAME) to access your calendar/string keyNSRemindersUsageDescription/key stringAllow $(PRODUCT_NAME) to access your reminders/string需要补充的是由于 iOS 17 引入了「完全访问」与「只写访问」的细分实际模块还管理着更多 plist 键。查看配置插件实现 plugin/src/withCalendar.ts 可以看到默认文案覆盖了以下键Info.plist 键作用默认文案NSCalendarsUsageDescription日历访问说明旧版/完全访问Allow $(PRODUCT_NAME) to access your calendarsNSCalendarsFullAccessUsageDescriptioniOS 17 日历完全访问同NSCalendarsUsageDescriptionNSCalendarsWriteOnlyAccessUsageDescriptioniOS 17 只写访问启用writeOnlyAccess时Allow $(PRODUCT_NAME) to add events to your calendarsNSRemindersUsageDescription提醒访问说明Allow $(PRODUCT_NAME) to access your remindersNSRemindersFullAccessUsageDescriptioniOS 17 提醒完全访问同NSRemindersUsageDescription也就是说withCalendar在默认情况下会补齐NSCalendarsFullAccessUsageDescription与NSRemindersFullAccessUsageDescription这两条新键以兼容 iOS 17 的分级授权弹窗。3.3 用 config plugin 自动完成双端配置在 managed 工程或使用 prebuild 的裸工程中更推荐通过app.json/app.config.js里的插件声明来注入权限同时可自定义提示文案。插件支持的 props 见 plugin/src/withCalendar.ts{ expo: { plugins: [ [ expo-calendar, { calendarPermission: 允许 $(PRODUCT_NAME) 访问您的日历, remindersPermission: 允许 $(PRODUCT_NAME) 访问您的提醒事项, writeOnlyAccess: false } ] ] } }calendarPermission对应NSCalendarsUsageDescription文案传false可移除该键remindersPermission对应NSRemindersUsageDescription文案writeOnlyCalendarPermission仅当writeOnlyAccess: true时生效对应NSCalendarsWriteOnlyAccessUsageDescriptionwriteOnlyAccess默认false开启 iOS 17 的只写访问模式——只允许创建事件不允许读取既有日历与事件也不允许增删改日历本身。开启后插件会写入 write-only 键并省略NSCalendarsFullAccessUsageDescription。插件同时会通过AndroidConfig.Permissions.withPermissions自动把android.permission.READ_CALENDAR与android.permission.WRITE_CALENDAR合并进 AndroidManifestplugin/src/withCalendar.ts无需再手工编辑清单。整份插件由createRunOncePlugin包装保证多次构建只执行一次。四、快速上手权限申请 创建事件无论哪个平台使用前都必须在运行时先申请权限。新 API 中权限方法支持传入{ writeOnly }该参数仅在 iOS 上生效writeOnly: true表示申请 iOS 17 的只写日历权限见 src/Calendar.tsimport * as Calendar from expo-calendar; async function createEventOnDefaultCalendar() { // 1. 申请日历访问权限 const perm await Calendar.requestCalendarPermissions(); if (perm.status ! granted) { console.warn(日历权限被拒绝); return; } // 2. iOS拿到系统默认日历 // Android 请改用 getCalendars() 并挑选 isPrimary / 可写日历 const calendar Calendar.getDefaultCalendarSync(); // 3. 在该日历下创建事件createEvent 内部已做日期字符串化 const event await calendar.createEvent({ title: Expo 技术分享, startDate: new Date(2026, 8, 10, 10, 0), // Date 对象会被自动处理 endDate: new Date(2026, 8, 10, 12, 0), allDay: false, location: 线上会议室, notes: 由 expo-calendar 创建, timeZone: Asia/Shanghai, }); console.log(已创建事件, event.id); }在组件中也可以使用官方提供的权限 HookuseCalendarPermissions()其内部封装了「查询 申请」两步src/Calendar.tsconst [calendarStatus, requestCalendarPermission] Calendar.useCalendarPermissions(); if (calendarStatus?.status ! granted) { return Button title授权日历访问 onPress{requestCalendarPermission} /; }五、新一代对象式 API 详解新版 API 的核心变化是返回的不再是纯 JSON 记录而是原生共享对象Shared Object。四个核心类在 src/Calendar.ts 中定义并挂到InternalExpoCalendar之上方法返回值都会被Object.setPrototypeOf校正回对应类原型从而保持方法链可用。5.1 ExpoCalendar表示一份系统日历既可通过Calendar.ExpoCalendar.get(calendarId)直接定位也可由getCalendars()、createCalendar()等工厂函数返回。它的关键成员方法src/Calendar.ts方法说明createEvent(details)在该日历下创建事件details中不可包含creationDate、lastModifiedDate、originalStartDate、isDetached、status、organizer等只读字段createReminder(details)创建提醒仅 iOSlistEvents(startDate, endDate)查询该日历在某个时间区间内的事件两个日期参数必填缺省会直接抛错listReminders(startDate?, endDate?, status?)按区间与完成状态过滤提醒仅 iOSupdate(details)更新日历的color与title内部对颜色做processColor转换delete()/addEventWithForm(options?)删除日历 / 唤起系统「新建事件」表单日历实体的属性可在类型定义中核对src/legacy/Calendar.tsid、title、source/sourceId、type、color、entityType、allowsModifications、allowedAvailabilities为通用字段isPrimary、name、ownerAccount、timeZone、allowedReminders、allowedAttendeeTypes、isVisible、isSynced、accessLevel标注为Android 平台字段。5.2 顶层函数跨实体操作入口除了类方法模块还导出一组顶层函数// 列出设备上全部日历iOS 上可按 entityType 过滤 EVENT / REMINDER const calendars await Calendar.getCalendars(Calendar.EntityTypes.EVENT); // 新建日历返回对应的 ExpoCalendar 共享对象 const created await Calendar.createCalendar({ title: 工作日程, color: #1E90FF }); // 跨多个日历查询事件传入日历 id 数组或 ExpoCalendar 对象数组均可 const events await Calendar.listEvents( calendars.map((c) c.id), new Date(2026, 8, 1), new Date(2026, 8, 30) ); // iOS弹出系统日历选择器 const picked await Calendar.presentPicker(); // iOS返回系统账号source列表 const sources Calendar.getSourcesSync();实现细节listEvents在 src/Calendar.ts 会把传入对象统一取id映射成字符串数组再透传原生层所有含Date的入参在跨桥前都会经过stringifyDateValues处理而返回的对象则重新提升原型以保留实例方法。createCalendar会强制丢弃调用方传入的id避免与系统生成 id 冲突并对color做processColor归一化src/Calendar.ts。5.3 ExpoCalendarEvent / ExpoCalendarAttendee / ExpoCalendarReminder事件对象除了静态ExpoCalendarEvent.get(eventId)之外还支持src/Calendar.tsgetAttendees()读取该事件的全部参与者createAttendee(attendee)新增参与者update(details)修改标题、地点、时区、URL、备注、闹钟、重复规则、可用性、起止时间、是否全天等可修改字段以ModifiableEventProperties为准见 src/ExpoCalendar.types.tsdelete()删除事件getOccurrenceSync(options)把重复事件解析为具体某一次发生同步方法。ExpoCalendarAttendee提供update/deleteExpoCalendarReminder仅 iOS提供update/delete/ 静态get(reminderId)。Reminder 可修改字段包括title、location、timeZone、url、notes、alarms、recurrenceRule、startDate、dueDate、completed、completionDate可完整覆盖「到期、完成、完成时刻」这套 Reminders 语义src/ExpoCalendar.types.ts。5.4 提醒权限与只写访问iOS 上读/写 Reminders 需要单独授权模块提供独立方法src/Calendar.tsconst reminderPerm await Calendar.requestRemindersPermissions(); const reminderStatus await Calendar.getRemindersPermissions();对应 Hook 为useRemindersPermissions()。值得注意的是它在非 iOS 平台不抛异常而是返回一个固定DENIED的PermissionResponse以便 UI 层免 try/catch 直接渲染降级态src/Calendar.ts。而requestCalendarPermissions({ writeOnly: true })则用于 iOS 17 的只写场景——应用只能“把事件写进系统日历”无法读取既有数据隐私面更小、审核更友好。是否启用该模式需与 3.3 节插件中的writeOnlyAccess保持一致因为 plist 键full access vs write-only由插件决定。六、平台差异速查能力AndroidiOS默认日历getDefaultCalendarSync()不可用抛UnavailabilityError可用getSourcesSync()/getSourceAsync()不可用可用Reminder提醒整套 API不可用可用Reminders 应用presentPicker()系统日历选择器不可用可用按EntityTypes过滤不支持该入参语义EVENT与REMINDER日历字段侧重isPrimary、ownerAccount、accessLevel、allowedReminders等type、entityType、sourceId等打开系统事件页API 30 需queries声明VIEWcontent使用系统事件控制器七、Legacy API从*Async方法迁移如果你之前使用旧版 API形如Calendar.createEventAsync需要注意根入口expo-calendar现在已经不再导出这些旧方法。从单元测试 src/tests/Calendar-test.native.ts 可见直接调用根入口上的 legacy 占位方法会打印expo-calendar/legacy相关提示并reject引导开发者显式改用旧子路径相关警告逻辑见 src/legacyWarnings.ts。三种入口的差异同样由该测试文件断言入口内容expo-calendar.新版对象式 APIExpoCalendar、ExpoCalendarEvent、createCalendar、getCalendars、listEvents等expo-calendar/next新版 API 的显式别名便于旧代码原地换路径expo-calendar/legacy旧版*Async方法全集src/legacy/Calendar.ts旧版方法覆盖非常全面包括getCalendarsAsync、createCalendarAsync、updateCalendarAsync、deleteCalendarAsync、getDefaultCalendarAsync、getEventsAsync、getEventAsync、createEventAsync、updateEventAsync、deleteEventAsync、createEventInCalendarAsync、openEventInCalendarAsync、editEventInCalendarAsync、getAttendeesForEventAsync、createAttendeeAsync、updateAttendeeAsync、deleteAttendeeAsync、getRemindersAsync、getReminderAsync、createReminderAsync、updateReminderAsync、deleteReminderAsync、getSourcesAsync、getSourceAsync、requestPermissionsAsync等函数列表见 src/legacy/Calendar.ts。迁移建议新代码直接使用expo-calendar主入口维护旧代码可先改导入路径到expo-calendar/legacy以恢复功能再按模块结构Calendar/Event/Attendee/Reminder的对象化方法逐步替换*Async调用。八、双端原生实现与工程组织源码视角expo-calendar 是一个标准的 Expo Module模块清单见 expo-module.config.jsonAndroid、iOS 代码与 TS 层按平台目录隔离。Android 侧完全基于系统CalendarContract与ContentResolver实现。例如唤起系统新建事件表单的 next/AddEventWithFormContract.kt 直接构造指向CalendarContract.Events.CONTENT_URI的 Intent并把标题、说明、地点、时区、全天标志、RRULE等塞进 extra。模块内部做了清晰的领域分层CalendarModule.kt/ next/CalendarNextModule.kt 负责模块注册下层是domain/repositoriesCalendar/Event/Attendee/Reminder/Instance 各仓库与domain/model中的实体与枚举映射把模块枚举映射回CalendarContract常量例如参与者角色映射到CalendarContract.Attendees.RELATIONSHIP_*。权限委托集中在 next/permissions 目录。iOS 侧以 EventKit 为底层。模块入口为 ios/CalendarModule.swift 与 ios/Next/CalendarNextModule.swift权限请求分别委托给CalendarPermissionsRequester、RemindersPermissionsRequester、CalendarWriteOnlyNextPermissionsRequester实现 iOS 17 分级plist 键集中维护在 ios/Next/CalendarPlistKeys.swift事件编辑/查看对话框在 ios/Dialogs 下实现。产物配置见 ios/ExpoCalendar.podspec。测试方面TS 层用 mock 原生桥的方式校验三种入口的导出与 legacy 告警src/tests/Calendar-test.native.tsAndroid 层则针对各 Repository 与 Mapper 编写了 Kotlin 单元测试例如 next/domain/repositories 下的 Calendar/Event/Attendee/Reminder/Instance 仓库测试与 EventRecurrenceRulesTest.kt重复规则解析。这些测试是理解「模块枚举 → 系统常量」映射关系的最佳入口。九、常用枚举与取值参考模块导出了大量字符串枚举均定义于 src/legacy/Calendar.ts 并统一经主入口 re-exportsrc/Calendar.ts枚举取值说明EntityTypesevent、reminder日历/事件所属应用类型iOSFrequencydaily、weekly、monthly、yearly重复频率AvailabilitynotSupported、busy、free、tentative、unavailable日程可用性CalendarTypelocal、caldav、exchange、subscribed、birthdays、unknown日历类型iOSEventStatusnone、confirmed、tentative、canceled事件状态SourceTypelocal、exchange、caldav、mobileme、subscribed、birthdays账号来源类型iOSAttendeeRoleunknown、required、optional等参与者角色AttendeeStatusaccepted、declined、invited、tentative、none等参与者状态AttendeeType依系统关系常量映射参与者类型ReminderStatus依 iOS 语义映射提醒完成筛选状态另有AlarmMethod、EventAccessLevel、CalendarAccessLevel、DayOfTheWeek、MonthOfTheYear等配套枚举可配合Alarm、RecurrenceRule类型构造提醒与重复规则DaysOfTheWeek支持单周几或序数组合详见 src/legacy/Calendar.ts。Alarm类型既支持absoluteDate也支持relativeOffset偏移触发具体字段以 src/legacy/Calendar.ts 起的Alarm/AlarmLocation定义为准。十、写在最后从 packages/expo-calendar/README.md 出发可以看到expo-calendar 是一个“小而完整”的 SDK 模块README 给出安装与最小原生配置真正的 API 深度沉淀在 src/Calendar.ts 的类型与方法实现中。若需在本仓库中继续深挖可重点阅读四个文件src/Calendar.tsTS 门面与权限、src/ExpoCalendar.types.ts实体与可修改字段、plugin/src/withCalendar.ts权限自动配置、以及双端原生 ModuleAndroid / iOS。它们共同回答了同一个问题如何用一套 TypeScript API把 React Native 应用安全、可靠地接进两大操作系统的日历体系。【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考