Как я могу поддерживать порядок отображения в UITableView, используя Core Data? - PullRequest
11 голосов
/ 30 октября 2009

У меня возникли проблемы с тем, чтобы мои сущности Core Data играли хорошо и упорядоченно при использовании UITableView.

Я прошел через несколько уроков и других вопросов здесь, в StackOverflow, но, похоже, нет ясного или элегантного способа сделать это - я действительно надеюсь, что что-то упустил.

У меня есть одна сущность Core Data с атрибутом int16, которая называется displayOrder. Я использую NSFetchRequest, который был отсортирован по «displayOrder», чтобы вернуть данные для моего UITableView. Все, кроме переупорядочения, соблюдается. Вот мой (неэффективный) метод moveRowAtIndePath:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {         

    NSUInteger fromIndex = fromIndexPath.row;  
    NSUInteger toIndex = toIndexPath.row;

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];  
    affectedObject.displayOrderValue = toIndex;

    [self FF_fetchResults];


    for (NSUInteger i = 0; i < [self.fetchedResultsController.fetchedObjects count]; i++) {  
        FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];  
        NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, i);  
        otherObject.displayOrderValue = i;  
    }

    [self FF_fetchResults];  
}

Может кто-нибудь указать мне хороший пример кода или посмотреть, что я делаю не так? Отображение табличного представления обновляется, и через мои сообщения журнала я вижу, что свойство displayOrder обновляется. Это просто не всегда сохраняет и перезагружает, и что-то чувствует себя очень не в своей реализации (за исключением расточительной итерации всех моих объектов FFO).

Заранее благодарим за любой совет, который вы можете дать.

Ответы [ 4 ]

27 голосов
/ 30 октября 2009

Я посмотрел на ваш код, и он мог бы работать лучше:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {         

    NSUInteger fromIndex = fromIndexPath.row;  
    NSUInteger toIndex = toIndexPath.row;

    if (fromIndex == toIndex) {
        return;
    }

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];  
    affectedObject.displayOrderValue = toIndex;

    NSUInteger start, end;
    int delta;

    if (fromIndex < toIndex) {
        // move was down, need to shift up
        delta = -1;
        start = fromIndex + 1;
        end = toIndex;
    } else { // fromIndex > toIndex
        // move was up, need to shift down
        delta = 1;
        start = toIndex;
        end = fromIndex - 1;
    }

    for (NSUInteger i = start; i <= end; i++) {
        FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];  
        NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta);  
        otherObject.displayOrderValue += delta;
    }

    [self FF_fetchResults];  
}
2 голосов
/ 17 февраля 2010

(Это подразумевается как комментарий к ответу gerry3 выше, но я пока не могу комментировать вопросы и ответы других пользователей.)

Небольшое улучшение для решения gerry3 - очень элегантное. Если я не ошибаюсь, строка

otherObject.displayOrderValue += delta;

будет фактически выполнять указатель арифметику, если displayOrderValue не является примитивным типом. Который может быть не то, что вы хотите. Вместо этого, чтобы установить значение сущности, я предлагаю:

otherObject.displayOrderValue = [NSNumber numberWithInt:[otherObject.displayOrderValue intValue] + delta];

Это должно корректно обновить свойство вашей сущности и избежать любых сбоев EXC_BAD_ACCESS.

1 голос
/ 08 декабря 2016

Здесь полное решение, как управлять индексированной таблицей с основными данными. Ваш атрибут называется displayOrder, я называю его index. Прежде всего, вам лучше разделить вид контроллера и модель. Для этого я использую контроллер модели, который является интерфейсом между представлением и моделью.

Есть 3 случая, когда вам нужно управлять, на которые пользователь может влиять через контроллер представления.

  1. Добавление нового объекта
  2. Удаление существующего объекта
  3. Изменение порядка объектов.

Первые два случая Добавление и удаление довольно просты. Delete вызывает подпрограмму с именем renewObjectIndicesUpwardsFromIndex для обновления индексов после удаленного объекта.

- (void)createObjectWithTitle:(NSString*)title {
    FFObject* object = [FFObject insertIntoContext:self.managedObjectContext];

    object.title = title;
    object.index = [NSNumber numberWithInteger:[self numberTotalObjects]];
    [self saveContext];
}

- (void)deleteObject:(FFObject*)anObject {
  NSInteger objectIndex = [anObject.index integerValue];
  [anObject deleteObject];
  [self renewObjectIndicesUpwardsFromIndex:objectIndex];
  [self saveContext];
}

- (void)renewObjectIndicesUpwardsFromIndex:(NSInteger)fromIndex {
  NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
  [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];

  NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d)", fromIndex];
  [fetchRequest setPredicate:predicate];

  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
  NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];

  [fetchRequest setSortDescriptors:sortDescriptors];

  NSError* fetchError = nil;
  NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];

  NSInteger index = fromIndex;
  for (FFObject* object in objects) {
    object.index = [NSNumber numberWithInteger:index];
    index += 1;
  }
  [self saveContext];
}

Прежде чем перейти к процедурам контроллера для повторного заказа, здесь часть в контроллере вида. Я использую bool isModifyingOrder похожий на этот ответ . Обратите внимание, что контроллер вида вызывает две функции в контроллере moveObjectOrderUp и moveObjectOrderDown. В зависимости от того, как вы отображаете объекты в табличном представлении - сначала самые новые или самые последние - вы можете переключать их.

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {

  isModifyingOrder = YES;

  NSUInteger fromIndex = sourceIndexPath.row;
  NSUInteger toIndex = destinationIndexPath.row;

  if (fromIndex == toIndex) {
    return;
  }

  FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex];

  NSInteger delta;
  if (fromIndex < toIndex) {
    delta = toIndex - fromIndex;
    NSLog(@"Moved down by %lu cells", delta);
    [self.objectController moveObjectOrderUp:affectedObject by:delta];
  } else {
    delta = fromIndex - toIndex;
    NSLog(@"Moved up by %lu cells", delta);
    [self.objectController moveObjectOrderDown:affectedObject by:delta];
  }

  isModifyingOrder = NO;
}

А вот и часть в контроллере. Это может быть написано лучше, но для понимания это может быть лучше.

- (void)moveObjectOrderUp:(FFObject*)affectedObject by:(NSInteger)delta {
  NSInteger fromIndex = [affectedObject.index integerValue] - delta;
  NSInteger toIndex = [affectedObject.index integerValue];

  if (fromIndex < 1) {
    return;
  }

  NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
  [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];

  NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index >= %d) AND (index < %d)", fromIndex, toIndex];
  [fetchRequest setPredicate:predicate];

  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
  NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];

  [fetchRequest setSortDescriptors:sortDescriptors];

  NSError* fetchError = nil;
  NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];

  for (FFObject* object in objects) {
    NSInteger newIndex = [object.index integerValue] + 1;
    object.index = [NSNumber numberWithInteger:newIndex];
  }

  affectedObject.index = [NSNumber numberWithInteger:fromIndex];

  [self saveContext];
}

- (void)moveObjectOrderDown:(FFObject*)affectedObject by:(NSInteger)delta {
  NSInteger fromIndex = [affectedObject.index integerValue];
  NSInteger toIndex = [affectedObject.index integerValue] + delta;

  NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
  [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]];

  NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d) AND (index <= %d)", fromIndex, toIndex];
  [fetchRequest setPredicate:predicate];

  NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
  NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];

  [fetchRequest setSortDescriptors:sortDescriptors];

  NSError* fetchError = nil;
  NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];

  for (FFObject* object in objects)
  {
    NSInteger newIndex = [object.index integerValue] - 1;
    object.index = [NSNumber numberWithInteger:newIndex];
  }

  affectedObject.index = [NSNumber numberWithInteger:toIndex];

  [self saveContext];
}

Не забудьте использовать второй BOOL в вашем контроллере вида для действия удаления, чтобы уведомление о перемещении ничего не делало. Я называю это isDeleting и помещаю это здесь.

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

  ...

  switch(type) {

    ...

    case NSFetchedResultsChangeMove:
        if (isDeleting == false) {
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:localIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:localNewIndexPath]withRowAnimation:UITableViewRowAnimationFade];
        }
        break;

    ...

  }
}
0 голосов
/ 30 августа 2016

Я думаю, что:

    affectedObject.displayOrderValue = toIndex;

должен быть размещен после:

   for (NSUInteger i = start; i <= end; i++) {
    FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];  
    NSLog(@"Updated %@ / %@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta);  
    otherObject.displayOrderValue += delta;
}

и до:

    [self FF_fetchResults];  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...