When I create an NSPredicate via EKEventStore predicateForRemindersInCalendars; and pass it to EKEventStore fetchRemindersMatchingPredicate:completion:^ I can loop through the reminders array provided by the completion code block, but when I try to store a reference to the reminders array, or create a copy of the array into a local variable or instance variable, both array's remain empty. The reminders array is never copied to them.
This is the method I am using, in the method, I create a predicate, pass it to the event store and then loop through all of the reminders logging their title via NSLog. I can see the reminder titles during runtime thanks to NSLog, but the local arrayOfReminders object is empty. I also try to add each reminder into an instance variable of NSMutableArray, but once I leave the completion code block, the instance variable remains empty.
Am I missing something here? Can someone please tell me why I can't grab a reference to all of the reminders for use through-out the app? I am not having any issues at all accessing and storing EKEvents, but for some reason I can't do it with EKReminders.
- (void)findAllReminders {
NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil];
__block NSArray *arrayOfReminders = [[NSArray alloc] init];
[self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
arrayOfReminders = [reminders copy]; //Does not work.
for (EKReminder *reminder in reminders) {
[self.remindersForTheDay addObject:reminder];
NSLog(@"%@", reminder.title);
}
}];
//Always = 0;
if ([self.remindersForTheDay count]) {
NSLog(@"Instance Variable has reminders!");
}
//Always = 0;
if ([arrayOfReminders count]) {
NSLog(@"Local Variable has reminders!");
}
}
The eventStore getter is where I perform my instantiation and get access to the event store.
- (EKEventStore *)eventStore {
if (!_eventStore) {
_eventStore = [[EKEventStore alloc] init];
//respondsToSelector indicates iOS 6 support.
if ([_eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
//Request access to user calendar
[_eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
NSLog(@"iOS 6+ Access to EventStore calendar granted.");
} else {
NSLog(@"Access to EventStore calendar denied.");
}
}];
//Request access to user Reminders
[_eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
if (granted) {
NSLog(@"iOS 6+ Access to EventStore Reminders granted.");
} else {
NSLog(@"Access to EventStore Reminders denied.");
}
}];
} else { //iOS 5.x and lower support if Selector is not supported
NSLog(@"iOS 5.x < Access to EventStore calendar granted.");
}
for (EKCalendar *cal in self.calendars) {
NSLog(@"Calendar found: %@", cal.title);
}
[_eventStore reset];
}
return _eventStore;
}
Lastly, just to show that I am initializing my remindersForTheDay instance variable using lazy instantiation.
- (NSMutableArray *)remindersForTheDay {
if (!_remindersForTheDay) _remindersForTheDay = [[NSMutableArray alloc] init];
return _remindersForTheDay;
}
I've read through the Apple documentation and it doesn't provide any explanation that I can find to answer this. I read through the Blocks Programming docs and it states that you can access local and instance variables without issues from within a block, but for some reason, the above code does not work.
Any help would be greatly appreciated, I've scoured Google for answers but have yet to get this figured out.
Thanks everyone!
Johnathon.
UPDATE:
I have since created a new app that does nothing but instance an event store, setup a table view, setup a KVO and updates the UI as per the recommendations provided. The code below is the app in its entirety. The UITableView is never updated with the reminders content. The app launches, and stares at me. The reloadData method invocation should eventually have the UI updated with the reminder data.
The reminder data exists in self.reminders, because my NSLog outputs that I have 372 objects in the array.
Lastly, if I touch the UITableView on the device, the app crashes with "'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'".
@import EventKit;
#import "RTViewController.h"
@interface RTViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) EKEventStore *eventStore;
@property (strong, nonatomic) NSArray *reminders;
@property (nonatomic) BOOL accessGranted;
@property (strong, nonatomic) NSDate *date;
@end
@implementation RTViewController
- (EKEventStore *)eventStore {
if (!_eventStore) {
_eventStore = [[EKEventStore alloc] init];
if ([_eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
[_eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
if (granted) self.accessGranted = YES;
else self.accessGranted = NO;
}];
}
[_eventStore reset];
}
return _eventStore;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.reminders = [[NSArray alloc] init];
self.date = [NSDate date]; //Returns NSDate allocated/init to NOW
NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil]; //nil will cause all calendars to be used.
//Ran on different thread.
[self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
_reminders = [reminders copy]; //NEVER happens.
[self updateReminders];
}];
//Setup the "radio" and watch ourself for when self.reminders is changed from the other thread
[self addObserver:self forKeyPath:@"reminders" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//If no data exists, then this will return 0 so no rows created.
return [self.reminders count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];
if (self.reminders) {
EKReminder *reminder = self.reminders[indexPath.row];
cell.detailTextLabel.text = reminder.title;
}
return cell;
}
//Called by self when self.remidners is changed. - NEVER gets called.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self.tableView reloadData];
}
- (void)updateReminders {
NSLog(@"%d objects found in array", [self.reminders count]);
[self.tableView reloadData];
}
@end