I'm trying to create a small application that will fetch all events and will show them in a tableView.
My specific questions are:
fetchEvents()
method is implemented correctly?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.
}
}
}
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.