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
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)
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?
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).
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:
Check the box Background Fetch in the Capabilities section of your application
Add the following to application(_:didFinishLaunchingWithOptions:)
:
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With