2013-06-30 2 views
0

Когда я создаю NSPredicate через EKEventStore predicateForRemindersInCalendars; и передать его в EKEventStore fetchRemindersMatchingPredicate: завершение:^Я могу пропустить массив напоминаний, предоставляемый блоком кода завершения, но когда я пытаюсь сохранить ссылку на массив напоминаний или создать копию массива в локальной переменной или экземпляре переменная, оба массива остаются пустыми. Массив напоминаний никогда не копируется с ними.Не удается получить ссылку на массив EKReminder, полученный из fetchRemindersMatchingPredicate

Это метод, который я использую в методе, я создаю предикат, передаю его в хранилище событий, а затем перебираю все напоминания, записывая их заголовок через NSLog. Я вижу заголовки напоминаний во время выполнения благодаря NSLog, но локальный объект arrayOfReminders пуст. Я также пытаюсь добавить каждое напоминание в переменную экземпляра NSMutableArray, но после того, как я покинул блок кода завершения, переменная экземпляра остается пустой.

Я что-то упустил? Может кто-нибудь, пожалуйста, скажите мне, почему я не могу получить ссылку на все напоминания для использования через приложение? У меня нет никаких проблем при доступе и хранении EKEvents, но по какой-то причине я не могу сделать это с помощью 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!"); 
    } 
} 

EventStore getter - это то место, где я выполняю свой экземпляр и получаю доступ к хранилищу событий.

- (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; 
} 

Наконец, просто чтобы показать, что я инициализация моего переменного экземпляра remindersForTheDay используя ленивый экземпляр.

- (NSMutableArray *)remindersForTheDay { 
    if (!_remindersForTheDay) _remindersForTheDay = [[NSMutableArray alloc] init]; 

    return _remindersForTheDay; 
} 

Я прочитал документацию Apple и не дает никаких объяснений, которые я могу найти, чтобы ответить на это. Я читаю через Blocks Programming docs и заявляет, что вы можете получить доступ к локальным и переменным экземпляра без проблем изнутри блока, но по некоторым причинам приведенный выше код не работает.

Любая помощь будет принята с благодарностью, я подсчитал Google ответы, но еще не понял это.

Спасибо всем!

Johnathon.

UPDATE:

С тех пор я создал новое приложение, которое не делает ничего, кроме, например хранилище событий, настройки вида таблицы, настроить КВО и обновляет пользовательский интерфейс в соответствии с рекомендациями, содержащимися. Код ниже - это приложение полностью. UITableView никогда не обновляется содержимым напоминаний. Приложение запускается и смотрит на меня. В результате вызова метода reloadData в конечном итоге должен быть обновлен пользовательский интерфейс с данными напоминания.

Данные напоминания существуют в self.reminders, потому что мой NSLog выводит, что у меня есть 372 объекта в массиве.

Наконец, если я касаюсь UITableView на устройстве, приложение вылетает с «NSInternalInconsistencyException», причина: «UITableView dataSource должен вернуть ячейку из 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 
+0

Первой проблемой с обновлением является то, что вы обращаетесь непосредственно к '_reminders', вместо использования' self.reminders'. Первый никогда не будет запускать KVO. Вторая проблема может заключаться в том, что вы можете обновлять пользовательский интерфейс (вызывающий 'reloadData') вне основного потока. Я рекомендую использовать что-то вроде 'dispatch_async (dispatch_get_main_queue(),^{[self.tableView reloadData];}'. В-третьих, я не вижу, что вы регистрируете ячейки, поэтому вам нужно проверить, что 'cell'' nil' и создать его с нуля. Любой базовый учебник 'UITableView' должен показать вам, как это сделать. – yonosoytu

+0

Я действительно сделал это в тот же вечер в воскресенье, когда пользовательский интерфейс обновления перешел на основной поток. Начиная с iOS 6, t нужно зарегистрировать ячейки, представление таблицы будет экземпляром нового, для вас одного из них не существует, при условии, что ячейка была создана в построителе интерфейса (моя была). Ввод вызова пользовательского интерфейса обновления в основной поток и настройка KVO исправил проблему для меня. Спасибо за помощь. –

+0

Кроме того, в документации на яблоки сказано, что вы можете обращаться к переменным экземпляра напрямую, а не к их свойствам изнутри блока кода, поэтому причина, по которой я обращался с помощью _reminders. напоминания = напоминания; в отличии t и вызвал его изнутри кодового блока. –

ответ

1

Проблема вы видите, что «завершение блока» исполняет «много позже» (машинного времени), чем код, который вы пометить как «Всегда = 0».

Вообще говоря, если блок называется «завершением» в API Apple, он будет выполняться асинхронно: Apple сохранит ваш блок, запустит запрос на выбор, а затем вызовет ваш блок с результатами. Результаты не будут готовы, когда метод fetchRemindersMatchingPredicate:completion: вернется, только когда вызывается ваш блок завершения. Это подтверждается documentation, где они говорят: «Этот метод набирает напоминания асинхронно».

Итак, вам нужно переместить логику в функцию обратного вызова ...

- (void)findAllReminders { 
    NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil]; 

    // I removed the arrayOfReminders, I think it wasn’t necessary, and was just to show 
    // your problem. 

    [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) { 
    for (EKReminder *reminder in reminders) { 
     [self.remindersForTheDay addObject:reminder]; 
     // You probably were seing this logging, right? 
     NSLog(@"%@", reminder.title); 
    } 
    [self refreshUI]; 
    }]; 
} 

- (void)refreshUI { 
    // Do whatever you need to do with the UI in refreshUI, inside it, the 
    // self.remindersForTheDay will be already filled. 
} 

Проблема знает, ваш метод eventStore, так как вы можете видеть, что это также «завершение» обратный вызов, она не всегда будет способный сразу вернуть значение, поэтому вам нужно немного изменить свою логику.

// Instead of returning the value directly, we will return the value in our own 
// completion callback. 
// The first argument will be a block that accepts an EKEventStore as parameter. 
- (void)performWithEventStore:(void (^)(EKEventStore *eventStore))completion { 
    if (!completion) return; // if no completion callback is provided, just return. 

    if (!_eventStore) { 
    _eventStore = [[EKEventStore alloc] init]; 

    if ([_eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]) { 
     // Request access to user calendar 
     [_eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { 
     if (granted) { 
      // We have been granted access, invoke our completion, and let them do the 
      // work 
      // Be careful, because here, we are in an arbitrary queue, you might want to 
      // switch to the main queue here or later. 
      completion(_eventStore); 
     } else { 
      // you haven’t been given access, you should probably show an error 
      NSLog(@"Access to EventStore calendar denied."); 
      // you might want to nilify _eventStore here. 
     } 
     }]; 

     // I think you cannot use the same store to access reminders and event, but I’m 
     // not sure. You might need to different instances of EKEventStore (following 
     // this example, two methods: performWithEventsStore and 
     // performWithRemindersStore) 
    } else { //iOS 5.x and lower support if Selector is not supported 
     // Because 5.x gives access immediately, just call our completion block 
     completion(_eventStore); 
    } 
    } else { 
    // A previous call to this method created the event store, just invoke the 
    // callback with the saved event store. 
    completion(_eventStore); 
    } 
} 

Затем метод findAllReminders должны быть изменены следующим образом:

- (void)findAllReminders { 
    NSPredicate *predicate = [self.eventStore predicateForRemindersInCalendars:nil]; 

    [self performWithEventStore:^(EKEventStore *eventStore) { 
    // We are using the eventStore parameter of the block 
    [eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) { 
     for (EKReminder *reminder in reminders) { 
      [self.remindersForTheDay addObject:reminder]; 
      // You probably were seing this logging, right? 
      NSLog(@"%@", reminder.title); 
     } 
     [self refreshUI]; 
    }]; 
    }]; 
} 

Да, это блок завершения внутри блока завершения. Это связано с тем, что здесь вы имеете дело с двумя асинхронными процессами: запрашиваете авторизацию для пользователя и извлекаете события из хранилища событий.

Надеюсь, это поможет!

+0

Хотел бы я купить тебе пиво! Итак, есть ли причина, по которой вы вызываете performWithEventStore:^для проверки авторизации вместо того, чтобы просто делать это при первом экземпляре экземпляра? Кроме того, поскольку этот код находится в моей модели, и моя модель не имеет доступа к моему пользовательскому интерфейсу (где мне нужно будет добавить метод refreshUI), следует ли добавить делегата в ViewController к моей модели? Поэтому я могу вызвать [self.delegate refreshUI]; ? Эти данные хранятся в модели, которую UITableViewCell использует для представления данных конечному пользователю. –

+0

А, я думаю, у меня есть решение. Я настрою что-то, используя [Key-Value Observing] (http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html). Я буду возиться с этим сегодня вечером и посмотреть, смогу ли я связать его с моим контроллером просмотра. –

+0

Я обновил свое оригинальное сообщение, чтобы показать новый пример. К сожалению, проблема до сих пор остается нерешенной. Я не могу получить EKReminders, хранящиеся в блоке кода, в пользовательский интерфейс. Есть ли у вас дополнительные предложения? –

Смежные вопросы