Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing calendar events/alerts in iOS EventKit?

I'm writing my first iOS app (I'm tired of missing event reminders and want to have a rule based reminder that, for example for certain calendars and certain people, will ring a different, louder tone, 10, 5 and 0 minutes before the event)

The first part is to get access to the calendar, and thanks to this great demo, I have that sort of covered. Got the app running on my iPhone and I can see my calendar events.

(well, the first part is to figure out swift, and general iOS basic, which I'm still working on)

The second part is where I wanted to ask before I spend hours researching. I have two tasks left to do

  1. either a background task to periodically check the calendar for new/updated events, or ability to programmatically subscribe to some sort of an event bus any calendar updates (new events, event changes)

  2. schedule notifications on a given time (I'll probably use this: How can I schedule local notification for the following scenario?)

How do I accomplish #1?

like image 280
Eran Medan Avatar asked Apr 13 '17 03:04

Eran Medan


3 Answers

I don’t think this is possible to accomplish without a design change.

Applications do not generally run in the background, and outside of very specific cases where Apple allows it (voip, music, location, etc.), your application will be suspended. There is the background refresh API, but that is not reliable enough.

Note that EKEventStoreChangedNotification notifications are only good when your process is running. If your process dies (due to memory pressure, device reboot, user killing the app, etc.), you will not be notified about changes when the app is launched, and you will still have to iterate over the meetings and look for changes, so you have to develop your own mechanism to iterate the EventKit database (perhaps within a certain timeframe).

If it’s a personal app, there are some tricks you can do to continue running in the background, such as playing a silent music file, voip background API, location, etc.). All these methods can keep your app running in the background, but will take a toll on battery life, and will not be approved by Apple upon submission. One way to have a consistent background runtime is to have a push notification service set a silent push to wake the app up periodically, and let it do its logic. This has the potential to launch the app in case it has been killed in the background (but not if the user killed it).

like image 146
Léo Natan Avatar answered Oct 18 '22 22:10

Léo Natan


There is a limit on what actions an application can carry out in the background. Background fetch normally involves fetching information from an external source so that your app can appear up to date once the user returns.

Background Refresh

When enabled, iOS monitors usage patterns to determine when to fetch new data. You are not able to decide when a background fetch is issued and it should not be used to do critical updates. In your project, the application could fetch new calendar events when the system feels it is an appropriate time. You can implement Background Fetch like so:

  1. Check the box Background Fetch in the Capabilities section of your application

  2. Add the following to application(_:didFinishLaunchingWithOptions:):

    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
    
  3. Finally, implement this function in your AppDelegate.

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

If you can compromise manual update scheduling, background refresh would be your best option. At the moment, there is no way to manually request a background update.

like image 3
Alexander MacLeod Avatar answered Oct 18 '22 20:10

Alexander MacLeod


To achieve #1 either a background task to periodically check the calendar for new/updated events, or ability to programmatically subscribe to some sort of an event bus any calendar updates (new events, event changes)

You can do following.

First of all to check new/updated calendar event we don't need to run any background task.

We can get the update for calendar events using .EKEventStoreChanged notification as followed in viewWillAppear method.

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

Handle calendar event changes (new / updated) EKEventStore changes as given below.

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
            }
        })
    })
}

So whenever there are any event changes (add/update/delete) we can get updates.

Additionally, when you create any event you get eventIdentifier from EKEvent object.

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")
    }
}

And get event using following method.

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

Please let me know if you need any more clarification.

like image 3
Jayeshkumar Sojitra Avatar answered Oct 18 '22 20:10

Jayeshkumar Sojitra