Fetch events from EKEventStore and show in tableView in Swift iOS8
ekeventstore, ios, ios8, swift, tableview
Solution
First off, You should be doing the rest of the work in your `fetchEvents` function inside the callback for `eventStore.requestAccessToEntityType`:
func fetchEvents() -> NSMutableArray {
eventStore.requestAccessToEntityType(EKEntityType()) completion: {
granted, error in
... the rest of the code ...
})
}
of course now that it's async, you'll need to return the data in a callback instead of returning it:
func fetchEvents(completed: (NSMutableArray) -> ()) {
eventStore.requestAccessToEntityType(EKEntityType()) completion: {
granted, error in
let endDate = NSDate(timeIntervalSinceNow: 604800*10); //This is 10 weeks in seconds
let predicate = self.eventStore.predicateForEventsWithStartDate(NSDate(), endDate: NSDate(), calendars: nil)
let events = NSMutableArray(array: self.eventStore.eventsMatchingPredicate(predicate))
completed(events)
})
}
But now you have another problem... you're using self in the closure, thereby capturing a reference, which can lead to a "reference cycle". In this particular case, I'm pretty sure that won't happen because `requestAccessToEntityType()` won't keep a reference to your closure (I'm pretty sure), but it's a good idea to use `[weak self]` whenever you you use `self` inside a callback function:
once you've done that, self is an optional though, so you need to make sure it's not nil…
func fetchEvents(completed: (NSMutableArray) -> ()) {
eventStore.requestAccessToEntityType(EKEntityType()) completion: { [weak self]
granted, error in
if let strongSelf = self {
let endDate = NSDate(timeIntervalSinceNow: 604800*10); //This is 10 weeks in seconds
let predicate = strongSelf.eventStore.predicateForEventsWithStartDate(NSDate(), endDate: NSDate(), calendars: nil)
let events = NSMutableArray(array: strongSelf.eventStore.eventsMatchingPredicate(predicate))
completed(events)
}
})
}
but looking a little closer, we never use `self` just `self.eventStore`, so we can capture a weak reference to that directly instead:
func fetchEvents(completed: (NSMutableArray) -> ()) {
eventStore.requestAccessToEntityType(EKEntityType()) completion: { [weak weakEventStore = self.eventStore]
granted, error in
if let eventStore = weakEventStore {
let endDate = NSDate(timeIntervalSinceNow: 604800*10); //This is 10 weeks in seconds
let predicate = eventStore.predicateForEventsWithStartDate(NSDate(), endDate: NSDate(), calendars: nil)
let events = NSMutableArray(array: eventStore.eventsMatchingPredicate(predicate))
completed(events)
}
})
}
finally, you really ought to check for errors instead of ignoring them, (`granted` could be false, meaning the user didn't give you access, or there could have been an error (so you need to check if `error` is nil (`if error { /* handle failure */ }`)
PS - I have working code which uses event kit on github which you may find useful.
Problem
I'm trying to create a small application that will fetch all events and will show them in a tableView. My specific questions are: - Is my `fetchEvents()` method is implemented correctly? - Where should I call the `fetchEvent()` method in order to show the list when the app opens (is it in `viewDidLoad`?) and to refresh it after the addition/edit of a new event? Thanks! This is my code for the `MasterViewController.swift` file: ``` import UIKit import EventKitUI class MasterViewController: UITableViewController , EKEventEditViewDelegate{ var objects = NSMutableArray() let eventStore = EKEventStore() override func awakeFromNib() { super.awakeFromNib() } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.navigationItem.leftBarButtonItem = self.editButtonItem() let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:") self.navigationItem.rightBarButtonItem = addButton //self.fetchEvents() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func insertNewObject(sender: AnyObject) { let controller = EKEventEditViewController() eventStore.requestAccessToEntityType(EKEntityType(), completion: {granted, error in }) controller.eventStore = eventStore controller.editViewDelegate = self self.presentModalViewController(controller, animated: true) let indexPath = NSIndexPath(forRow: 0, inSection: 0) self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } func eventEditViewController(controller: EKEventEditViewController!, didCompleteWithAction action: EKEventEditViewAction) { self.dismissModalViewControllerAnimated(true) } func fetchEvents() -> NSMutableArray { eventStore.requestAccessToEntityType(EKEntityType(), completion: {granted, error in }) let endDate = NSDate(timeIntervalSinceNow: 604800*10); //This is 10 weeks in seconds let predicate = self.eventStore.predicateForEventsWithStartDate(NSDate(), endDate: NSDate(), calendars: nil) var events = NSMutableArray(array: self.eventStore.eventsMatchingPredicate(predicate)) return events /* var indexes = NSMutableIndexSet(index: 5) indexes.addIndex(4) objects.insertObjects(events, atIndexes: indexes) */ // Create the start date components /* NSDateComponents *oneDayAgoComponents = [[NSDateComponents alloc] init]; oneDayAgoComponents.day = -1; let oneDayAgo = currentCalendar.date dateByAddingComponents:oneDayAgoComponents*/ } // #pragma mark - Segues override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "showDetail" { let indexPath = self.tableView.indexPathForSelectedRow() let object = objects[indexPath.row] as NSDate (segue.destinationViewController as DetailViewController).detailItem = object } } // #pragma mark - Table View override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return objects.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell let object = objects[indexPath.row] as NSDate cell.textLabel.text = object.description return cell } override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { // Return false if you do not want the specified item to be editable. return true } override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { objects.removeObjectAtIndex(indexPath.row) tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) } else if editingStyle == .Insert { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. } } } ```