前言
昨天给朋友写个提醒 App,本来想用推送通知,结果他说能不能直接加到系统日历里?翻了下苹果文档发现系统框架中 EventKit 有个 EKAlarm 可以干这事。试了下还挺好用,就是文档写得有点绕。今天就来讲讲这个框架的使用。
EKAlarm 能干啥
简单说就是给日历事件加提醒:
时间提醒:提前多少分钟告诉你
地点提醒:到了地方或者走了就提醒
Mac 版本功能更多,能播放声音、发邮件,不过手机上用得少。
权限
用日历 API 最头疼的就是权限,苹果管得很严。不过也理解,谁都不想自己的日程被乱翻。
先在 Info.plist 里加这两行:
<key>NSCalendarsFullAccessUsageDescription</key>
<string>用来创建日程提醒</string>
<key>NSRemindersFullAccessUsageDescription</key>
<string>管理你的提醒事项</string>
苹果现在权限分得很细,有三种:
完全访问:能读能写,功能最全
只写权限:只能创建不能读,比如车票 App
拒绝访问:啥都干不了
// 请求完全访问权限
let status = try await eventStore.requestFullAccessToEvents()
// 堆代码 duidaima.com
// 只要写入权限(iOS 17+)
let writeStatus = try await eventStore.requestWriteOnlyAccessToEvents()
怎么使用
最简单得就是提前几分钟提醒:
let alarm = EKAlarm(relativeOffset: -600) // 600 秒就是 10 分钟
event.addAlarm(alarm)
负数表示提前,正数表示延后。一个事件可以加多个提醒。
完整代码
import EventKit
class CalendarManager {
let eventStore = EKEventStore()
func createEvent(title: String, startDate: Date, endDate: Date) {
guardlet calendar = eventStore.defaultCalendarForNewEvents else { return }
let event = EKEvent(eventStore: eventStore)
event.title = title
event.startDate = startDate
event.endDate = endDate
event.calendar = calendar
let alarm = EKAlarm(relativeOffset: -600) // 提前 10 分钟
event.addAlarm(alarm)
try? eventStore.save(event, span: .thisEvent)
}
}

这样用户就能在日历 App 里看到事件,时间到了会有通知。
几个 Tips
多个提醒
重要事情我一般设 3 个提醒:
event.addAlarm(EKAlarm(relativeOffset: -86400)) // 一天前
event.addAlarm(EKAlarm(relativeOffset: -3600)) // 一小时前
event.addAlarm(EKAlarm(relativeOffset: -600)) // 十分钟前
指定时间提醒
let alarm = EKAlarm(absoluteDate: someDate)
比如不管会议几点开,都在早上 9 点提醒下。
地点提醒
这个功能挺酷地,走到某个地方自动提醒:
let geoAlarm = EKAlarm()
geoAlarm.proximity = .enter
设置有点复杂,不过实现后挺有用得。走到公司门口就提醒你今天有啥安排。
在 SwiftUI 里用起来也很简单:
struct CalendarView: View {
@Stateprivatevar permissionManager = CalendarPermissionManager()
@Stateprivatevar showingAlert = false
var body: some View {
VStack {
Text("日历权限: \(statusText)")
Button("创建提醒") {
Task {
if await requestPermissionIfNeeded() {
// 创建日历事件
} else {
showingAlert = true
}
}
}
}
.alert("需要日历权限", isPresented: $showingAlert) {
Button("去设置") {
// 跳转到设置页面
iflet settingsUrl = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsUrl)
}
}
Button("取消", role: .cancel) { }
}
.onAppear {
permissionManager.checkCurrentStatus()
}
}
privatevar statusText: String {
switch permissionManager.calendarStatus {
case .granted: return"已授权 ✅"
case .denied: return"被拒绝 ❌"
case .notDetermined: return"未决定 ⏳"
}
}
privatefunc requestPermissionIfNeeded() async -> Bool {
if permissionManager.calendarStatus == .granted {
returntrue
}
return await permissionManager.requestCalendarAccess()
}
}
写在最后
说实话这个功能还挺实用,集成到 App 里比推送自然多了。用户也习惯在日历里管理时间。就是适配有点烦,不过也没办法,苹果 API 年年改。你在项目中用过吗?感觉怎么样?