Подтвердить что ты не робот

Перенастройка UITableView с основными данными

Возможный дубликат:
Как реализовать переупорядочение записей CoreData?

Я пытаюсь найти образец кода, который показывает, как обрабатывать перемещение/переупорядочение ячеек в таблицеView, когда ячейка использует fetchedResultsController (т.е. вместе с основными данными). Я получаю moveRowAtIndexPath: звоню в мой источник данных, но я не могу найти правильную комбинацию черной магии, чтобы заставить таблицу/данные правильно распознать изменения.

Например, когда я перемещаю строку 0 в строку 2, а затем отпускаю, он "выглядит" корректно. Затем я нажимаю "Готово". Строка (1), которая соскользнула до заполнения строки 0, все еще имеет вид режима редактирования (минус и перемещение значков), а остальные строки ниже возвращаются к нормальному виду. Если я затем прокручу вниз, так как строка 2 (изначально 0, помните?) Приближается к вершине, она полностью исчезает.

WTF. Мне нужно каким-то образом аннулировать fetchedResultsController? Всякий раз, когда я устанавливаю его на ноль, я получаю сбои. Должен ли я освободить его вместо этого? Я в сорняках?

Вот что я сейчас получил там...

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

    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];

    /*
     Update the links data in response to the move.
     Update the display order indexes within the range of the move.
     */

    if (fromIndexPath.section == toIndexPath.section) {

        NSInteger start = fromIndexPath.row;
        NSInteger end = toIndexPath.row;
        NSInteger i = 0;
        if (toIndexPath.row < start)
            start = toIndexPath.row;
        if (fromIndexPath.row > end)
            end = fromIndexPath.row;
        for (i = start; i <= end; i++) {
            NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section];
            LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath];
            //[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]];
            link.order = [NSNumber numberWithInteger:i];
            [managedObjectContext refreshObject:link mergeChanges:YES];
            //[managedObjectContext insertObject:link];
        }

    }
    // Save the context.
    NSError *error;
    if (![context save:&error]) {
        // Handle the error...
    }

}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {

    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    if (self.theTableView != nil)
        [self.theTableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    if (self.theTableView != nil) {
        [self.theTableView endUpdates];
    }
}
4b9b3361

Ответ 1

Здесь то, что официально работает сейчас, с удалением, перемещением и вставками. Я "проверяю" заказ при каждом изменении действия, влияющего на заказ.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section != kHeaderSection) {

        if (editingStyle == UITableViewCellEditingStyleDelete) {

            @try {
                LinkObj * link = [self.fetchedResultsController objectAtIndexPath:indexPath];

                debug_NSLog(@"Deleting at indexPath %@", [indexPath description]);
            //debug_NSLog(@"Deleting object %@", [link description]);

                if ([self numberOfBodyLinks] > 1) 
                    [self.managedObjectContext deleteObject:link];

            }
            @catch (NSException * e) {
                debug_NSLog(@"Failure in commitEditingStyle, name=%@ reason=%@", e.name, e.reason);
            }

        }
        else if (editingStyle == UITableViewCellEditingStyleInsert) {
            // we need this for when they click the "+" icon; just select the row
            [theTableView.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
        }
    }
}

- (BOOL)validateLinkOrders {        
    NSUInteger index = 0;
    @try {      
        NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects];

        if (fetchedObjects == nil)
            return NO;

        LinkObj * link = nil;       
        for (link in fetchedObjects) {
            if (link.section.intValue == kBodySection) {
                if (link.order.intValue != index) {
                    debug_NSLog(@"Info: Order out of sync, order=%@ expected=%d", link.order, index);

                    link.order = [NSNumber numberWithInt:index];
                }
                index++;
            }
        }
    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in validateLinkOrders, name=%@ reason=%@", e.name, e.reason);
    }
    return (index > 0 ? YES : NO);
}


- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects];  
    if (fetchedObjects == nil)
        return;

    NSUInteger fromRow = fromIndexPath.row + NUM_HEADER_SECTION_ROWS;
    NSUInteger toRow = toIndexPath.row + NUM_HEADER_SECTION_ROWS;

    NSInteger start = fromRow;
    NSInteger end = toRow;
    NSInteger i = 0;
    LinkObj *link = nil;

    if (toRow < start)
        start = toRow;
    if (fromRow > end)
        end = fromRow;

    @try {

        for (i = start; i <= end; i++) {
            link = [fetchedObjects objectAtIndex:i]; //
            //debug_NSLog(@"Before: %@", link);

            if (i == fromRow)   // it our initial cell, just set it to our final destination
                link.order = [NSNumber numberWithInt:(toRow-NUM_HEADER_SECTION_ROWS)];
            else if (fromRow < toRow)
                link.order = [NSNumber numberWithInt:(i-1-NUM_HEADER_SECTION_ROWS)];        // it moved forward, shift back
            else // if (fromIndexPath.row > toIndexPath.row)
                link.order = [NSNumber numberWithInt:(i+1-NUM_HEADER_SECTION_ROWS)];        // it moved backward, shift forward
            //debug_NSLog(@"After: %@", link);
        }
    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in moveRowAtIndexPath, name=%@ reason=%@", e.name, e.reason);
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {    
    @try {
        switch (type) {
            case NSFetchedResultsChangeInsert:
                [theTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self validateLinkOrders];
                break;
            case NSFetchedResultsChangeUpdate:
                break;
            case NSFetchedResultsChangeMove:
                self.moving = YES;
                [self validateLinkOrders];
                break;
            case NSFetchedResultsChangeDelete:
                [theTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self validateLinkOrders];
                break;
            default:
                break;
        }
    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in didChangeObject, name=%@ reason=%@", e.name, e.reason);
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.theTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.theTableView 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.
    @try {
        if (self.theTableView != nil) {
            //[self.theTableView endUpdates];
            if (self.moving) {
                self.moving = NO;
                [self.theTableView reloadData];
                //[self performSelector:@selector(reloadData) withObject:nil afterDelay:0.02];
            }
            [self performSelector:@selector(save) withObject:nil afterDelay:0.02];
        }   

    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in controllerDidChangeContent, name=%@ reason=%@", e.name, e.reason);
    }
}

Ответ 2

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

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

    for (i = start; i <= end; i++) {
            NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section];
            LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath];
            //[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]];
            link.order = [NSNumber numberWithInteger:i];
            [managedObjectContext refreshObject:link mergeChanges:YES];
            //[managedObjectContext insertObject:link];
    }

Ловушка заключается в том, что вы фактически не меняете порядок в базовой модели. Эти indexPaths из UITableViewController, он сообщает вам, что пользователь перетаскивает их между точками, и вам необходимо обновить базовые данные. Но fetchedResultsController всегда находится в порядке сортировки, поэтому пока вы не изменили эти свойства, ничего не изменилось.

Дело в том, что они не были перемещены, вас зовут, чтобы сообщить вам, что вам нужно их перемещать (путем настройки сортируемого свойства). Вам действительно нужно что-то большее:

NSNumber *targetOrder = [fetchedResultsController objectAtIndexPath:toIndexPath];
LinkObj *link = [fetchedResultsController objectAtIndexPath:FromPath];
link.order = targetOrder;

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

Ответ 3

Когда вы перемещаете строку в виде таблицы, , вы фактически перемещаете блок других строк (состоящий хотя бы из одной строки) в другое направление одновременно. Хитрость заключается только в обновлении свойства displayOrder этого блока и перемещенного элемента.

Сначала убедитесь, что свойство displayOrder для всех строк задано в соответствии с текущим порядком отображения таблиц. Нам не нужно сохранять контекст здесь, мы сохраним его позже, когда закончится фактическая операция перемещения:

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];
    [_tableView setEditing:editing animated:animated];
    if(editing) {
        NSInteger rowsInSection = [self tableView:_tableView numberOfRowsInSection:0];
       // Update the position of all items
       for (NSInteger i=0; i<rowsInSection; i++) {
          NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
          SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath];
          NSNumber *newPosition = [NSNumber numberWithInteger:i];
          if (![curObj.displayOrder isEqualToNumber:newPosition]) {
             curObj.displayOrder = newPosition;
          }
       }
    }
}

Тогда вам нужно только обновить позицию перемещенного элемента и всех элементов междуIndexPath и toIndexPath:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    NSInteger moveDirection = 1;
    NSIndexPath *lowerIndexPath = toIndexPath;
    NSIndexPath *higherIndexPath = fromIndexPath;
    if (fromIndexPath.row < toIndexPath.row) {
        // Move items one position upwards
        moveDirection = -1;
        lowerIndexPath = fromIndexPath;
        higherIndexPath = toIndexPath;
    }

    // Move all items between fromIndexPath and toIndexPath upwards or downwards by one position
    for (NSInteger i=lowerIndexPath.row; i<=higherIndexPath.row; i++) {
        NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:fromIndexPath.section];
        SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath];
        NSNumber *newPosition = [NSNumber numberWithInteger:i+moveDirection];
        curObj.displayOrder = newPosition;
    }

    SomeManagedObject *movedObj = [_fetchedResultsController objectAtIndexPath:fromIndexPath];
    movedObj.displayOrder = [NSNumber numberWithInteger:toIndexPath.row];
    NSError *error;
    if (![_fetchedResultsController.managedObjectContext save:&error]) {
        NSLog(@"Could not save context: %@", error);
    }
}

Ответ 4

Лучший ответ на самом деле в комментарии Клинта Харриса по вопросу:

http://www.cimgf.com/2010/06/05/re-ordering-nsfetchedresultscontroller

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

- (void)tableView:(UITableView *)tableView 
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath 
      toIndexPath:(NSIndexPath *)destinationIndexPath;
{  
  NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy];

  // Grab the item we're moving.
  NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath];

  // Remove the object we're moving from the array.
  [things removeObject:thing];
  // Now re-insert it at the destination.
  [things insertObject:thing atIndex:[destinationIndexPath row]];

  // All of the objects are now in their correct order. Update each
  // object displayOrder field by iterating through the array.
  int i = 0;
  for (NSManagedObject *mo in things)
  {
    [mo setValue:[NSNumber numberWithInt:i++] forKey:@"displayOrder"];
  }

  [things release], things = nil;

  [managedObjectContext save:nil];
}

Документация Apple также содержит важные подсказки:

https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html

Это также упоминается в Как реализовать переупорядочение записей CoreData?

Процитировать документацию Apple:

Пользовательские обновления

В общем, NSFetchedResultsController предназначен для реагирования на изменения на уровне модели. Если вы разрешаете пользователю изменять порядок строк в таблице, то ваша реализация методов делегата должна принять это в счет.

Обычно, если вы разрешаете пользователю изменять порядок строк таблицы, ваша модель объект имеет атрибут, который указывает его индекс. Когда пользователь перемещается строка, вы соответствующим образом обновляете этот атрибут. Это, однако, имеет что приводит к тому, что контроллер замечает изменение, и поэтому сообщить своему делегату об обновлении (используя Контроллер: didChangeObject: atIndexPath: forChangeType: newIndexPath:). Если вы просто используете реализацию этого метода, показанного в "Типичном Use", то делегат пытается обновить представление таблицы. Таблица однако, он уже находится в соответствующем состоянии из-за действий пользователей.

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

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

    if (!changeIsUserDriven) {
        UITableView *tableView = self.tableView;
        // Implementation continues...

Ответ 5

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
    [self.pairs exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
    [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.02];
}

- (void)reloadData{
    [table reloadData];
}

Таблица не может перезагрузиться во время ее перемещения, перезагрузить после задержки, и все будет в порядке.

Ответ 6

Извините, Грег, я уверен, что я делаю что-то неправильно, но вы отвечаете, что не работает для меня.

Хотя все мои объекты правильно проверяются, когда я выхожу из режима редактирования, одна из строк зависает (элементы управления редактирования не исчезают), после чего ячейки не реагируют должным образом.

Возможно, моя проблема в том, что я не знаю, как использовать свойство moving, которое вы установили (self.moving = YES). Не могли бы вы прояснить это? Большое вам спасибо.

Хорхе

Ответ 8

[< > isEditing] может использоваться для определения того, включено ли редактирование таблицы или нет. Вместо того, чтобы откладывать его, как было предложено, используя следующий оператор

[table performSelector: @selector (reloadData) withObject: nil afterDelay: 0,02];