2012-09-14 3 views
8

Po przeczytaniu kilkudziesięciu podobnych pytań, chciałbym zacząć od tego stwierdzenia: "ZOSTAŁEM ustawiony delegat NSFetchedResultsController, to nie działało." .NSFetchedResultsController nie wyświetla nowych wstawek/usuwa pobrane wartości po aktualizacji

Więc mam prosty TableViewController, którego komórki są wypełnione NSFetchedResultsController. Oto FRC Init:

@property (strong, nonatomic) NSFetchedResultsController *frc; 

... 

- (NSFetchedResultsController *)frc 
{ 
    if (!_frc) 
    { 
     NSError *error = nil; 
     NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product]; 
     request.sortDescriptors = [NSArray arrayWithObjects: 
           [NSSortDescriptor sortDescriptorWithKey:@"product_group.product_group_name" ascending:YES], 
           [NSSortDescriptor sortDescriptorWithKey:f_product_name ascending:YES], 
           nil]; 
     _frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:[DTGGlobalSettings sharedInstance].moc sectionNameKeyPath:@"product_group.product_group_name" cacheName:nil]; 
     _frc.delegate = self; 
     [_frc performFetch:&error]; 
     if (error) 
     { 
      NSLog(@"Error while fetching products: %@!", error.userInfo); 
     } 
    } 

    return _frc; 
} 

Mam też metody delegata NSFetchedResultsController realizowane z niektórych etykiet debugowania:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates. 
    NSLog(@"controllerWillChangeContent"); 
    [self.tableView beginUpdates]; 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

    UITableView *tableView = self.tableView; 

switch(type) { 

    case NSFetchedResultsChangeInsert: 
     NSLog(@"didChangeObject - insert"); 
     [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeDelete: 
     NSLog(@"didChangeObject - delete"); 
     [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeUpdate: 
     NSLog(@"didChangeObject - update"); 
     [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 
     break; 

    case NSFetchedResultsChangeMove: 
     NSLog(@"didChangeObject - move"); 
     [tableView deleteRowsAtIndexPaths:[NSArray 
              arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     [tableView insertRowsAtIndexPaths:[NSArray 
              arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
} 
} 


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { 

switch(type) { 

    case NSFetchedResultsChangeInsert: 
     NSLog(@"didChangeSection - insert"); 
     [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 

    case NSFetchedResultsChangeDelete: 
     NSLog(@"didChangeSection - delete"); 
     [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
     break; 
} 
} 


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { 
// The fetch controller has sent all current change notifications, so tell the table view to process all updates. 
NSLog(@"controllerDidChangeContent"); 
[self.tableView endUpdates]; 
} 

W moim navigationBar Mam przycisk odświeżania, jej funkcją jest odpalić w katalogu produktów metoda, która wykonuje następujące czynności: 1. Pobiera produkty ze zdalnego serwera MySQL 2. Jeśli produkt z identyfikatorem nie istnieje - tworzy go i wypełnia wszystkie pola 3. Jeśli istnieje, wszystkie pola są aktualizowane zgodnie z pobrane wartości:

Po wierszu 2 i 3 magazyn zostanie zapisany. Używam pojedynczego obiektu NSManagedObjectContext dla wszystkich operacji CoreData w aplikacji.

Kiedy uruchamiam aplikację po raz pierwszy, TVC jest pusta, ponieważ nie było jeszcze żadnych produktów pobranych. Kiedy klikam Odśwież, nowe produkty są pobierane i wstawiane do ManagedObjectContext, ale NSManagedObjectContext nie widzi/nie słyszy żadnych zmian: nie są wywoływane metody, więc żadne nowe wiersze nie są dodawane do TVC. Po ponownym uruchomieniu aplikacji nowe produkty są wprowadzane do TVC bez żadnego problemu.

Jeśli ponownie naciśniesz przycisk odświeżania, gdy są tam jakieś produkty, po krótkim czasie wszystkie znikną. Niektóre debugowanie pokazało mi, że jeśli zdarzy się to po aktualizacji pól encji (faktycznie wartości są takie same) i zapisaniu sklepu. Metody delegatów tym razem działają jak uroki i dla każdego zaktualizowanego i zapisanego kontrolera jednostki: didChangeObject: atIndexPath: forChangeType: newIndexPath jest wywoływana z forChangeType = NSFetchedResultsChangeDelete.

Pytanie nr 1: dlaczego NSFetchedResultsController nie widzi wstawionych elementów bez restartu aplikacji? Pytanie # 2: dlaczego NSFetchedResultsController zaznacza już pobrane obiekty jako "nieistniejące" (?) I usuwa je z TVC po zapisaniu w sklepie?

Będę wdzięczny za każdą pomoc i pomysły, jest walka z tym problemem dla całego ostatniego tygodnia; (

UPD1 (dla Jody): Oto kilka szczegółów mogę używać niestandardowych DTGGlobalSettings klasy podzielić się vars. . całej aplikacji Tak właśnie init, jego właściwość sharedInstance:

+(DTGGlobalSettings *)sharedInstance 
{ 
    static DTGGlobalSettings *myInstance = nil; 

    if (nil == myInstance) 
    { 
     myInstance = [[[self class] alloc] init]; 
     // set values here 
     // try to acces MOC to init CoreData 
     [myInstance moc];   
     myInstance.baseURL = @"http://local.app/"; 
} 
return myInstance; 
} 

ROZPOCZ.NIEMOŻL CoreData stos użyłem przykład z dokumentacji firmy Apple, ponieważ z jakiegoś powodu nie widzę pole „Używaj CoreData” podczas tworzenia nowego Jestem pewien, że widziałem to już wcześniej w Xc Oda, ale teraz nie mam; ((Xcode 4.4.1):

#pragma mark Core Data stack 

- (NSManagedObjectContext *) moc { 

if (_moc != nil) { 
    return _moc; 
} 
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
if (coordinator != nil) { 
    _moc = [[NSManagedObjectContext alloc] init]; 
    [_moc setPersistentStoreCoordinator: coordinator]; 
} 
return _moc; 
} 


- (NSManagedObjectModel *)managedObjectModel { 

if (_managedObjectModel != nil) { 
    return _managedObjectModel; 
} 
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];  
return _managedObjectModel; 
} 


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 

if (_persistentStoreCoordinator != nil) { 
    return _persistentStoreCoordinator; 
} 
NSURL *storeUrl = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 
storeUrl = [storeUrl URLByAppendingPathComponent:DOC_NAME]; 

NSError *error; 
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]]; 

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) { 
    NSLog(@"Error adding a store to the coordinator: %@, %@", error, error.userInfo); 
}   
return _persistentStoreCoordinator; 
} 

-(void)saveDataStore 
{ 
    NSError *error; 
    if (![self.moc save:&error]) NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
} 

do 'moc' (ManagedObjectContext) własność DTGGlobalSettings jest następnie wykorzystywany wszędzie w aplikacji, gdzie potrzebny jest dostęp do CoreData.

Teraz do drugiej części, aktualizowanie bazy danych.Dostaję nowe jednostki w formacie JSON ze zdalnego serwera WWW, przechodzę przez wynikowy słownik i wywołuję metodę createFromDictionary dla każdej jednostki znalezionej w wynikach zapytania. Oto kod:

+(Product *)createFromDictionary:(NSDictionary *)entityData inContext:(NSManagedObjectContext *)moc performUpdate:(BOOL)update 
{ 
Product *result; 
if (update) 
{ 
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:e_product]; 
    request.predicate = [NSPredicate predicateWithFormat:@"%K = %@", f_product_id, [entityData objectForKey:f_product_id]]; 
    result = [[DTGGlobalSettings sharedInstance].moc executeFetchRequest:request error:nil].lastObject; 
    if (!result) 
    { 
     NSLog(@"Error updating Product ID %@! Cannot fetch entity.", [entityData objectForKey:f_product_id]); 
     return nil; 
    } 
} else 
{ 
    result = [NSEntityDescription insertNewObjectForEntityForName:e_product inManagedObjectContext:[DTGGlobalSettings sharedInstance].moc]; 
} 

result.product_id = [[DTGGlobalSettings sharedInstance].numFormatter numberFromString:[entityData objectForKey:f_product_id]]; 
result.product_image = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[DTGGlobalSettings sharedInstance].baseURL stringByAppendingString:[entityData objectForKey:f_product_image]]]]; 
result.product_name = [entityData objectForKey:f_product_name]; 

return result; 
} 

Kiedy zabraknie jednostek fethed wzywam [[DTGGlobalSettings sharedInstance] saveDataStore] (patrz wyżej). W tym momencie zniknęły istniejące rzędy w TVC (jeśli były). W przypadku, gdy dodawałem nowe jednostki, w miejscu, w którym nic się nie stało, TVC nie aktualizuje wierszy.

+0

Napisałeś nieprawidłowy kod. Interesujące bity to sposób importowania danych do bazy danych oraz sposób ustawiania podstawowego stosu danych zarówno dla kontrolera importu, jak i pobranego wyniku. –

+0

Dodałem pewne szczegóły, o które prosiłeś w UPD1. Mam nadzieję, że teraz jest jaśniej, jak skończyłem z tym ... – Arseniy

+0

jakieś myśli na temat aktualizacji? – Arseniy

Odpowiedz

14

Właściwie, myślę, że twój pierwszy komentarz do edycji poprzedzającej miał wszystkie niezbędne informacje. Po prostu tego nie przeczytałem (nie lubię, gdy muszę ciągle czytać długie wiersze kodu, a czasem po prostu tego nie robię - moje złe).

Twój problem wydaje się być związany z deskryptorami sortowania FRC.

FRC nie radzi sobie zbyt dobrze z aktualizowaniem deskryptorów relacji.

Niektórzy twierdzą, że tak to zostało zaprojektowane. Twierdzę, że to błąd.

Zobacz ten artykuł na ten temat: Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view. Mam nadzieję, że zauważysz podobieństwa do swojej sprawy.

+0

To jest niesamowite! Właśnie szybko usunąłem deskryptory sortowania i ścieżkę nazwy sekcji i zadziałało! Nigdy bym nie zgadł, że to działa w ten sposób;) Dzięki! – Arseniy

+0

czy ten problem nadal istnieje? –

+0

Tak. https://insanitysauce.files.wordpress.com/2013/09/bsxgx3vciaax5g5.jpg –

1

Właśnie spędziłem czas z tym samym numerze i dowiedział się, co następuje: gdy sectionNameKeyPath odnosił się do pola relacji, które mogą przyjąć wartość nil, aktualizacja FRC nie będzie działać podczas wstawiania obiektów.

Może to być związane z ostrzeżeniem na konsoli Xcode, że sekcja zostanie utworzona dla obiektów z tym polem ustawionym na nil. Domyślam się, że ta sekcja miesza się z wewnętrzną pracą FRC.