É comportamento NSFetchedResultsControllerDelegate 'ChangeUpdate' quebrado?
-
19-09-2019 - |
Pergunta
Os docs para NSFetchedResultsControllerDelegate fornecer o seguinte código de exemplo
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
Quando eu criar uma nova NSManagedObject, NSFetchedResultsChangeInsert incêndios (great!). Quando eu mudar o valor de um atributo (usado para o título da célula), o NSFetchedResultsChangeUpdate incêndios. Infelizmente, o novo título não exibir automaticamente a menos que eu recarregar a mesa, seção ou linha. De fato, se o novo nome faz com que o conjunto de resultados de tipo diferente, em seguida, NSFetchedResultsChangeMove incêndios e tudo está bem desde que recarrega código fornecidos toda a seção.
UITableView tem um método reloadRowsAtIndexPaths: withRowAnimation , então eu tentei usar isso sob a NSFetchedResultsChangeUpdate bloco de código. Ele faz de fato o trabalho ... mas a documentação para este método específico ler como se eu não preciso disso (aviso a última linha):
Recarregando uma linha faz com que o exibição de tabela para pedir a sua fonte de dados para uma nova célula para essa linha. A mesa anima que a nova célula, uma vez que anima a linha antiga fora. Chame esse método se você deseja alertar o usuário que o valor de uma célula está a mudar. Se, no entanto, notificando o usuário não é importante, ou seja, você só quer alterar o valor que uma célula é exibindo-você pode obter a célula para uma linha particular e definir o seu novo valor.
E sim, se eu registrar o que está acontecendo, quando
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
é invocado em um NSFetchedResultsChangeUpdate , é capaz de recuperar o valor mais recente 'nome' e colocá-lo em textLabel da célula. O nome simplesmente não está prestando na célula a menos que eu recarregá-lo. Mesmo se eu simplesmente clique na célula o nome aparece. Note-se que para recriar esse comportamento, você deve criar um novo objeto gerenciado e, em seguida, dar-lhe um nome que faz com que ele para classificar em primeiro lugar no NSFetchedResultsController. Dessa forma, o NSFetchedResultsChangeMove não fogo (que faz o trabalho uma vez que recarrega a seção).
Estou faltando alguma coisa ou é esse comportamento esperado? A 'discussão' para reloadRowsAtIndexPaths me leva a crer que eu deveria ser capaz de simplesmente definir textLabel da célula sem recarregar a linha, seção ou mesa.
Solução
Você deve chamar [cell setNeedsLayout]
e / ou [cell setNeedsDisplay]
para fazer com que o celular para se refrescado, dependendo da implementação do seu celular.
Se você compõe a célula de subviews como costumamos fazer, você confiar em - layoutSubviews
, então você deve chamar [cell setNeedsLayout]
.
Se você desenhar a célula diretamente com – drawRect:
, você deve chamar [cell setNeedsDisplay]
.
Se você usar tanto a composição e desenho, você deve ligar para ambos.
Outras dicas
Embora seja verdade que você não precisa recarregar o celular, a fim de que as alterações tenham lugar, você tem que lembrar que as caches iPhone vista de desenho, tanto quanto possível. Depois de ter configurado recentemente o seu celular, você precisa chamar setNeedsDisplay na célula, a fim de acionar o redesenho.
Embora não seja sido explicitamente indicado, você realmente tem que se certificar de que você incluir os métodos de delegado para controllerWillChangeContent: e controllerDidChangeContent:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
e
- (void)controllerDidChangeContent:(BSFetchedResultsController *)controller {
@try {
[self.tableView endUpdates];
}
@catch (NSException * e) {
NSLog(@"caught exception: %@: %@", [e name], [e description]);
}
@finally { }
}
O NSFRC pode disparar controlador múltipla: didChangeObject: atIndexPath: forChangeType: newIndexPath: métodos durante qualquer mudança, por isso não esperamos a linha da tabela para atualizar logo após cada um desses. Eles só vou atualizar depois que você chamar endUpdates sobre a mesa.
Chamando a lógica UI atualização em thread principal resolvido o meu problema (ambos [cell setNeedsLayout]
& [cell setNeedsDisplay]
não funciona para mim):
...
case NSFetchedResultsChangeUpdate:
dispatch_async(dispatch_get_main_queue(), {
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
});
break;
...
O que é mais (e não sobre esta questão, mas será útil), é melhor optar por usar o newIndexPath
se ele está disponível:
...
case NSFetchedResultsChangeUpdate:
dispatch_async(dispatch_get_main_queue(), {
NSIndexPath * targetIndexPath = (newIndexPath ?: indexPath)
[self configureCell:[tableView cellForRowAtIndexPath:targetIndexPath]
atIndexPath:targetIndexPath];
});
break;
...