Search code examples
iosswifttableviewios8ekeventstore

Fetch events from EKEventStore and show in tableView in Swift iOS8


I'm trying to create a small application that will fetch all events and will show them in a tableView.

My specific questions are:

  1. Is my fetchEvents() method is implemented correctly?
  2. 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.
    }
}
}

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.