在 iOS EventKit 中自定义日历 events/alerts?

Customizing calendar events/alerts in iOS EventKit?

我正在编写我的第一个 iOS 应用程序(我厌倦了错过事件提醒并希望有一个基于规则的提醒,例如对于某些日历和某些人,会响起不同的,声音更大,事件发生前 10、5 和 0 分钟)

第一部分是访问日历,多亏了 this 很棒的演示,我已经掌握了这些内容。在我的 iPhone 上安装了应用程序 运行,我可以看到我的日历活动。

(好吧,第一部分是弄清楚swift,一般iOS基础,我还在努力)

第二部分是我在花几个小时研究之前想问的地方。我还有两个任务要做

  1. 定期检查日历中是否有 new/updated 事件的后台任务,或者能够以编程方式订阅某种事件总线的任何日历更新(新事件、事件更改)

  2. 在给定时间安排通知(我可能会使用这个:How can I schedule local notification for the following scenario?

我如何完成#1?

要检查日历数据库的更改,有一个通知:

EKEventStoreChanged

Posted whenever changes are made to the Calendar database, including adding, removing, and changing events or reminders. Individual changes are not described. When you receive this notification, you should refetch all EKEvent and EKReminder objects you have accessed, as they are considered stale. If you are actively editing an event and do not wish to refetch it unless it is absolutely necessary to do so, you can call the refresh method on it. If the method returns true, you do not need to refetch the event.

添加一个观察者:

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

并实现处理通知的方法

func storeChanged(_ notification: Notification) {}

我认为如果不更改设计是不可能实现的。

应用程序通常不会 运行 在后台运行,并且在 Apple 允许的非常特殊的情况下(网络电话、音乐、位置等),您的应用程序将被暂停。有后台刷新API,但不够靠谱

请注意,EKEventStoreChangedNotification 通知仅在您的进程 运行ning 时有效。如果你的进程死了(由于内存压力、设备重启、用户杀死应用程序等),你将不会在应用程序启动时收到有关更改的通知,你仍然需要遍历会议并寻找更改,所以你必须开发自己的机制来迭代 EventKit 数据库(可能在特定时间范围内)。

如果是个人应用,您可以采取一些技巧在后台继续 运行ning,例如播放无声音乐文件、voip 背景 API、定位等。 ).所有这些方法都可以让您的应用程序 运行ning 在后台运行,但会消耗电池寿命,并且在提交后不会被 Apple 批准。拥有一致后台 运行time 的一种方法是让推送通知服务设置静默推送以定期唤醒应用程序,并让它执行其逻辑。这有可能在应用程序在后台被杀死的情况下启动应用程序(但如果用户杀死它则不会)。

应用程序可以在后台执行的操作是有限制的。后台获取通常涉及从外部来源获取信息,以便您的应用在用户 returns.

后显示为最新。

后台刷新

启用后,iOS 会监控使用模式以确定何时获取新数据。您不能 决定何时发出后台提取并且不应将其用于进行关键更新。在您的项目中,应用程序可以在系统认为合适的时间获取新的日历事件。您可以像这样实现后台获取:

  1. 选中应用程序“功能”部分中的“后台获取”复选框

  2. 将以下内容添加到 application(_:didFinishLaunchingWithOptions:):

    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
    
  3. 最后,在你的AppDelegate中实现这个功能。

    func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        // Perform calendar updates here
    }
    

如果您可以妥协手动更新计划,后台刷新将是您的最佳选择。目前无法手动请求后台更新。

要实现#1,要么定期检查日历中的 new/updated 事件的后台任务,要么能够以编程方式订阅某种事件总线的任何日历更新(新事件、事件更改)

您可以执行以下操作。

首先要检查 new/updated 日历事件,我们不需要 运行 任何后台任务。

我们可以使用 .EKEventStoreChanged 通知获取日历事件的更新,如下 viewWillAppear 方法。

NotificationCenter.default.addObserver(self, selector: #selector(eventStoreChanged:), name: .EKEventStoreChanged, object: eventStore)

处理日历事件更改(新/更新)EKEventStore 更改如下。

func eventStoreChanged(_ notification: Notification) {
    let ekEventStore: EKEventStore? = notification.object
    let now = Date()
    let offsetComponents = DateComponents()
    offsetComponents.day = 0
    offsetComponents.month = 4
    offsetComponents.year = 0
    let endDate: Date? = Calendar.current.date(byAddingComponents: offsetComponents, to: now, options: [])
    let ekEventStoreChangedObjectIDArray: [Any]? = (notification.userInfo?["EKEventStoreChangedObjectIDsUserInfoKey"] as? [Any])
    let predicate: NSPredicate? = ekEventStore?.predicateForEvents(withStartDate: now, endDate: endDate, calendars: nil)
    // Loop through all events in range
    ekEventStore?.enumerateEvents(matchingPredicate: predicate, usingBlock: {(_ ekEvent: EKEvent, _ stop: Bool) -> Void in
        // Check this event against each ekObjectID in notification
        (ekEventStoreChangedObjectIDArray as NSArray).enumerateObjects(usingBlock: {(_ ekEventStoreChangedObjectID: String, _ idx: Int, _ stop: Bool) -> Void in
            let ekObjectID: NSObject? = (ekEvent as? NSManagedObject)?.objectID
            if ekEventStoreChangedObjectID.isEqual(ekObjectID) {
                // EKEvent object is the object which is changed.
                stop = true
            }
        })
    })
}

因此,只要有任何事件变化 (add/update/delete),我们就可以得到更新。

此外,当您创建任何事件时,您会从 EKEvent 对象中获得 eventIdentifier

let eventStore : EKEventStore = EKEventStore()
eventStore.requestAccess(to: .event) { (granted, error) in

    if (granted) && (error == nil) {
        print("granted \(granted)")
        print("error \(error)")

        let event:EKEvent = EKEvent(eventStore: eventStore)

        event.title = "Event"
        event.startDate = Date()
        event.endDate = Date()
        event.calendar = eventStore.defaultCalendarForNewEvents
        do {
            try eventStore.save(event, span: .thisEvent)
        } catch let error as NSError {
            print("failed to save event with error : \(error)")
        }
        print("Saved Event id : \(event.eventIdentifier)")
    }
    else{

        print("failed to save event with error : \(error) or access not granted")
    }
}

并使用以下方法获取事件。

let event:EKEvent = eventStore?.event(withIdentifier: eventIdentifier)

如果您需要更多说明,请告诉我。

,目前无法为iOS日历构建专业功能后台任务(苹果Swift3.1版本)。

iOS后台状态非常有限(有很多原因可以保持电池寿命等)和"calendar background mode"在实际时刻还不存在(您可以查看官方 Apple 文档 Background modes for apps 可用的官方 Background modes for apps,Table 3-1)。始终保持在场,我们的移动设备具有所有物理限制,而不是 linux 服务器,但对于日历,我们也很不走运。

换句话说,系统不会在后台状态向您的应用传达新的日历更改,并且您永远不会使用iOS官方获得可靠的提醒应用日历。

您可以使用 EKEventStoreChanged 通知创建后台任务,但是当您的应用程序处于 "suspended state" 时会发生什么?您总是通过后台状态与您的应用程序对话,如您所见,没有获取日历更改的选项。

如果您制作了自定义日历应用程序也没有办法(因此不使用官方日历应用程序)因为您必须始终将后台状态伴随到外部服务器(远程通知可能是一个主意..):假设您没有 connection/internet 所以您的推送通知会延迟到达。在这种情况下,您的提醒不知道您在那个时间有一个特定的约会,您可能会被警告得太晚,这可能意味着一场彻底的灾难。