UICollectionViewController с NSFetchedResultsController не работает, когда обновляется свойство всех объектов, если контроллер отсортирован по этому свойству - PullRequest
0 голосов
/ 28 сентября 2019

У меня есть UICollectionViewController, это представление коллекции для некоторых объектов на карте.Таким образом, я могу обновить один элемент в выбранном результате (например, установить LIKE для одного из этого объекта).Это хорошо работает.Я вижу изменения немедленно.Но при попытке обновить свойство «Расстояние» всех Объектов, и одновременно контроллер отсортирован по этому свойству.Полученный результат не обновляется автоматически.И затем, во время прокрутки, повторно использованные ячейки не обновляются, и я вижу только те ячейки, которые были на дисплее, до того, как я начал обновлять свойство для всех объектов.Если бы я TouchUpInside первой ячейки в строке, например, это был бы объект 123, детализированная страница контроллера для другого объекта, этот объект, который должен оставаться в этой позиции после collectionView reloadData.


Incase I 'изменить дескрипторы сортировки с [fetchRequest setSortDescriptors:@[distanceAscending]];

на [fetchRequest setSortDescriptors:@[titleAscending]];

или [fetchRequest setSortDescriptors:@[titleAscending, distanceAscending]];

Это хорошо работает.


Incase Iменяю свойство «Расстояние» только одного объекта.Это хорошо работает.Контроллер сортирует Ячейки как следует.


Incase Я изменяю свойство "Дистанция" объектов, не выбранных на этот раз.Это хорошо работает.


Если я закрываю этот контроллер и снова открываю [fetchRequest setSortDescriptors:@[distanceAscending]];, работаю как надо


Я пытаюсь перезагрузить CollectionViewController различными способами[self.collectionView reloadData]; и reloadwithPredicateDefault.Но тот же результат.


Я пытаюсь изменить контекст управляемого объекта NSPrivateQueueConcurrencyType и NSMainQueueConcurrencyType.Но тот же результат.


MapObjectCollectionViewController.h

@interface MapObjectCollectionViewController : UICollectionViewController

MapObjectCollectionViewController.m

@interface MapObjectCollectionViewController ()<NSFetchedResultsControllerDelegate>

@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSManagedObjectContext* managedObjectContext;
@property (strong, nonatomic) NSPredicate * predicate1;
@property (strong, nonatomic) NSPredicate * predicate2;

#pragma mark - Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController
{


    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription* description =
    [NSEntityDescription entityForName:@"MapObj"
                inManagedObjectContext:self.managedObjectContext];

    [fetchRequest setEntity:description];



        _predicate1 = [NSPredicate predicateWithFormat:@"types.typeObjValue IN %@", self.selectionsTypes];
        _predicate2 = [NSPredicate predicateWithFormat:@"wiFi >= %i", 0]; 



    NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[_predicate1, _predicate2]];
    [fetchRequest setPredicate:predicate];


    NSSortDescriptor* titleAscending = [[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES];

    NSSortDescriptor* distanceAscending = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:NO];

        [fetchRequest setSortDescriptors:@[distanceAscending]];
        // [fetchRequest setSortDescriptors:@[titleAscending, distanceAscending]];

    NSFetchedResultsController *aFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.managedObjectContext
                                          sectionNameKeyPath:nil
                                                   cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _fetchedResultsController;
}

-(void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
      atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
     newIndexPath:(NSIndexPath *)newIndexPath {
    NSMutableDictionary *change = [[NSMutableDictionary alloc] init];
    switch(type) {
        case NSFetchedResultsChangeInsert:
            change[@(type)] = newIndexPath;
            break;
        case NSFetchedResultsChangeDelete:
            change[@(type)] = indexPath;
            break;
        case NSFetchedResultsChangeUpdate:
            change[@(type)] = indexPath;
            break;
        case NSFetchedResultsChangeMove:
            change[@(type)] = @[indexPath, newIndexPath];
            break;
    }
    [_itemChanges addObject:change];
}
- (void)reloadwithPredicateDefault {
    [NSFetchedResultsController deleteCacheWithName:nil];
    self.fetchedResultsController = nil;
    [self.fetchedResultsController performFetch:nil];
    [self.collectionView reloadData];
}
#pragma mark - UICollectionViewDataSource
- (NSManagedObjectContext*) managedObjectContext {

    if (!_managedObjectContext) {
        _managedObjectContext = [[DataManager sharedManager] managedObjectContext];
    }
    return _managedObjectContext;
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
    [self.collectionView performBatchUpdates:^{
        for (NSDictionary *change in self->_sectionChanges) {
            [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
                NSFetchedResultsChangeType type = [key unsignedIntegerValue];
                switch(type) {
                    case NSFetchedResultsChangeInsert:
                        [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];
                        break;
                    case NSFetchedResultsChangeDelete:
                        [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]];
                        break;
                    case NSFetchedResultsChangeMove:
                        break;
                    case NSFetchedResultsChangeUpdate:
                        break;
                }
            }];
        }
        for (NSDictionary *change in self->_itemChanges) {
            [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
                NSFetchedResultsChangeType type = [key unsignedIntegerValue];
                switch(type) {
                    case NSFetchedResultsChangeInsert:
                        [self.collectionView insertItemsAtIndexPaths:@[obj]];
                        break;
                    case NSFetchedResultsChangeDelete:
                        [self.collectionView deleteItemsAtIndexPaths:@[obj]];
                        break;
                    case NSFetchedResultsChangeUpdate:
                        [self.collectionView reloadItemsAtIndexPaths:@[obj]];
                        break;
                    case NSFetchedResultsChangeMove:
                        [self.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]];
                        break;
                }
            }];
        }
    } completion:^(BOOL finished) {
        self->_sectionChanges = nil;
        self->_itemChanges = nil;

    }];
}

DataManager.h

@property (readonly, strong, nonatomic) NSManagedObjectContext *mainPrivateManagedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

+ (DataManager*)sharedManager;

DataManager.m

@implementation DataManager

@synthesize mainPrivateManagedObjectContext = _mainPrivateManagedObjectContext;
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

+(DataManager*) sharedManager{

    static DataManager* manager = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[DataManager alloc] init];
    });

    return manager;
}

#pragma mark - Core Data stack


- (NSManagedObjectModel *)managedObjectModel {
    // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"ProjectName" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it.
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    // Create the coordinator and store

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"ProjectName.sqlite"];
    NSError *error = nil;
    // NSString *failureReason = @"There was an error creating or loading the application's saved data.";
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES} error:&error]) {
        NSLog(@"error = %@", error);
        // Report any error we got.

        [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]; //Удалить старую базу

        [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]; //Создать базу заново

    }
    return _persistentStoreCoordinator;
}


- (NSManagedObjectContext *)managedObjectContext {

    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
    if (_managedObjectContext != nil) {
        // . NSLog(@"get managedObjectContext");
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }

    _mainPrivateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_mainPrivateManagedObjectContext setPersistentStoreCoordinator:coordinator];

    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_managedObjectContext setParentContext:_mainPrivateManagedObjectContext];
    // . NSLog(@"get return managedObjectContext");
    return _managedObjectContext;
}

- (NSManagedObjectContext *)getContextForBGTask {
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

    [context setParentContext:self.managedObjectContext];
    return context;
}
- (NSArray*) allMapObj {

    NSFetchRequest* request = [[NSFetchRequest alloc] init];

    NSEntityDescription* description =
    [NSEntityDescription entityForName:@"MapObj"
                inManagedObjectContext:self.managedObjectContext];

    [request setEntity:description];

    NSError* requestError = nil;
    NSArray* resultArray = [self.managedObjectContext executeFetchRequest:request error:&requestError];
    if (requestError) {
        NSLog(@"%@", [requestError localizedDescription]);
    }
    return resultArray;
}
- (void)calculateDistanceWithCurrentLoaction:(CLLocation*) currentLoaction{
    NSFetchRequest* request = [[NSFetchRequest alloc] init];
    NSManagedObjectContext * bgcontext = [self getContextForBGTask];
    NSEntityDescription* description =
    [NSEntityDescription entityForName:@"MapObj"
                inManagedObjectContext:bgcontext];

    [request setEntity:description];

    NSError* requestError = nil;
    NSArray* resultArray = [bgcontext executeFetchRequest:request error:&requestError];
    if (requestError) {
        NSLog(@"%@", [requestError localizedDescription]);
    }
    for (MapObj *mapObject in resultArray) {

            CLLocation *endLocation = [[CLLocation alloc] initWithLatitude:[mapObject.latitude doubleValue] longitude:[mapObject.longitude doubleValue]];
            CLLocationDistance distance = [currentLoaction distanceFromLocation:endLocation];
            mapObject.distance = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%f", distance]];

    }
    [bgcontext updatedObjects];
    [self saveContextForBGTask:bgcontext];

}
- (void)saveContextForBGTask:(NSManagedObjectContext *)bgTaskContext {
    if (bgTaskContext.hasChanges) {

        [bgTaskContext performBlockAndWait:^{
            NSError *error = nil;

            [bgTaskContext save:&error];
        }];
        // Save default context
        [self saveDefaultContext:YES];
    }
}
- (void)saveDefaultContext:(BOOL)wait {
    if (_managedObjectContext.hasChanges) {
        [_managedObjectContext performBlockAndWait:^{
            // . NSLog(@"managed context = %@", _managedObjectContext);
            NSError *error = nil;
            [self->_managedObjectContext save:&error];
        }];
    }
    void (^saveMainPrivateManagedObjectContext) (void) = ^{
        NSError *error = nil;
        [self->_mainPrivateManagedObjectContext save:&error];
    };
    if ([_mainPrivateManagedObjectContext hasChanges]) {
        if (wait){
            // . NSLog(@"main context = %@", _mainPrivateManagedObjectContext);
            [_mainPrivateManagedObjectContext performBlockAndWait:saveMainPrivateManagedObjectContext];
        } else {
            [_mainPrivateManagedObjectContext performBlock:saveMainPrivateManagedObjectContext];
        }
    }
}

Ошибка в консоли:

2019-09-28 12:35:14.951873+0400 ProjectName[15695:4020487] *** Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore/UIKit-3698.140/UICollectionView.m:5972

CoreData: fault: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  attempt to perform an insert and a move to the same index path (<NSIndexPath: 0xa945f4d4afea737e> {length = 2, path = 0 - 4}) with userInfo (null)

Что я пропустил?

1 Ответ

0 голосов
/ 28 сентября 2019

В качестве простого решения было добавлено:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
   [self.collectionView performBatchUpdates:^{
   ......
   if(self->_itemChanges.count > 1){
       [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
   } else {
       for (NSDictionary *change in self->_itemChanges) {
           ...
       }
   }
   ......
}
...