Error de afirmación al usar UISearchDisplayController en UITableViewController

80

He intentado agregar una funcionalidad de búsqueda simple a un TableViewController en mi aplicación. Seguí el tutorial de Ray Wenderlich. Tengo un tableView con algunos datos, agregué la barra de búsqueda + el controlador de pantalla en el guión gráfico, y luego tengo este código:

#pragma mark - Table View
     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BreedCell" forIndexPath:indexPath];

        //Create PetBreed Object and return corresponding breed from corresponding array
        PetBreed *petBreed = nil;

        if(tableView == self.searchDisplayController.searchResultsTableView)
            petBreed = [_filteredBreedsArray objectAtIndex:indexPath.row];
        else
            petBreed = [_breedsArray objectAtIndex:indexPath.row];

        cell.accessoryType  = UITableViewCellAccessoryDisclosureIndicator;
        cell.textLabel.text = petBreed.name;

        return cell;
    }

#pragma mark - Search
    -(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
        [_filteredBreedsArray removeAllObjects];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.name contains[c] %@",searchString];
        _filteredBreedsArray = [[_breedsArray filteredArrayUsingPredicate:predicate] mutableCopy];

        return YES;
    }

    -(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
        // Tells the table data source to reload when scope bar selection changes

        [_filteredBreedsArray removeAllObjects];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.name contains[c] %@",self.searchDisplayController.searchBar.text];
        _filteredBreedsArray = [[_breedsArray filteredArrayUsingPredicate:predicate] mutableCopy];
        return YES;
    }

Las cosas estándar, pero cuando ingreso texto en la barra de búsqueda, se bloquea cada vez con este error:

2013-01-07 19:47:07.330 FindFeedo[3206:c07] *** Assertion failure in -[UISearchResultsTableView dequeueReusableCellWithIdentifier:forIndexPath:], /SourceCache/UIKit_Sim/UIKit-2372/UITableView.m:4460
2013-01-07 19:47:07.330 FindFeedo[3206:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier BreedCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

Entiendo que en iOS 6 el sistema de manejo y eliminación de la cola de las celdas cambió, y también que la búsqueda usa una vista de tabla diferente, así que pensé que el problema era que la vista de tabla de búsqueda con los resultados filtrados no conocía la celda, así que puse esto en mi opiniónDidLoad:

[self.searchDisplayController.searchResultsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"BreedCell"];

¡Y voilá! Funcionó ... Sólo la primera vez que buscas. Si vuelve a los resultados originales y vuelve a buscar, la aplicación se bloquea con el mismo error. Pensé en agregar todos los

if(!cell){//init cell here};

cosas al método cellForRow, pero ¿no va eso en contra del propósito de tener el método dequeueReusableCellWithIdentifier: forIndexPath:? De todos modos, estoy perdido. ¿Qué me estoy perdiendo? Ayuda por favor. Gracias de antemano por todo su tiempo (:

Alex.

acib708
fuente

Respuestas:

194

Intente usar self.tableView en lugar de tableView en dequeueReusableCellWithIdentifier:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"BreedCell"];

    //Create PetBreed Object and return corresponding breed from corresponding array
    PetBreed *petBreed = nil;

    if(tableView == self.searchDisplayController.searchResultsTableView)
        petBreed = [_filteredBreedsArray objectAtIndex:indexPath.row];
    else
        petBreed = [_breedsArray objectAtIndex:indexPath.row];

    cell.accessoryType  = UITableViewCellAccessoryDisclosureIndicator;
    cell.textLabel.text = petBreed.name;

    return cell;
}

Este código funciona bastante bien

Nota

Si tiene celdas de altura personalizadas, no use

[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

Usa esto en su lugar

[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
FunkyKat
fuente
4
¿No sería eso simplemente instanciar nuevas celdas de una tabla a otra indefinidamente? si sacar células de una tabla y
dárselas
1
Pensé que esto también funcionaría. No es así. En mi caso, intentar retirar la celda de mi tabla principal para la tabla de búsqueda a veces provoca un bloqueo.
Lee Probert
1
No funciona. Intente buscar, luego cancele (salga del modo de resultados superpuestos) y luego busque nuevamente. Choques.
Daniel
8
La solución propuesta todavía me plantea una excepción. Sin embargo, parece funcionar como se esperaba si lo reemplazo dequeueReusableCellWithIdentifier:forIndexPath:por dequeueReusableCellWithIdentifier:.
jweyrich
3
¡gracias! self.tableview en lugar de tableview era el problema. salvavidas!
Adamteale
14

La razón por la que funcionó muy bien en la primera ejecución, pero luego se bloqueó si salía de la tabla de resultados y regresaba para otra búsqueda es porque el controlador de pantalla de búsqueda está cargando uno nuevo UITableViewcada vez que ingresa al modo de búsqueda.

Por modo de búsqueda, quiero decir, ha tocado el campo de texto y ha comenzado a escribir, momento en el que se genera una vista de tabla para mostrar los resultados, saliendo de este modo que logró presionando el botón cancelar. Cuando toca el campo de texto por segunda vez y comienza a escribir nuevamente, esto es ingresar al "modo de búsqueda" por segunda vez.

Entonces, para evitar el bloqueo, debe registrar la clase de celda para que la vista de tabla use en el searchDisplayController:didLoadSearchResultsTableView:método delegado (desde UISearchDisplayDelegate) en lugar de en el viewDidLoadmétodo de los controladores .

Como sigue:

- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
{
    [tableView registerClass:[DPContentTableCell class] forCellReuseIdentifier:cellIdentifier];
    [tableView registerClass:[DPEmptyContentTableCell class] forCellReuseIdentifier:emptyCellIdentifier];
}

Esto me tomó por sorpresa porque en iOS 7 ... la vista de tabla se está reutilizando. Para que puedas inscribirte en la clase viewDidLoadsi lo prefieres. Por el bien de la herencia, mantendré mi registro en el método de delegado que mencioné.

Daniel
fuente
2
Desafortunadamente, esto no funciona con las celdas de prototipo dinámico de Storyboard.
Ortwin Gentz
Pero funciona muy bien con células registradas por nib. Buena solución.
Thomas Verbeek
12

Después de buscar, 'tableView' del método cellForRowAtIndexPath parece no ser una instancia de la Tabla que usted define. Entonces, puede usar una instancia de una tabla que define la celda. En vez de:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

Utilizar:

UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

(No use el método tableView de cellForRowAtIndexPath, use self.tableView ).

aqubi
fuente
2
+1. Necesitaba self.tableView en mi rápida implementación de este concepto.
davidethell
3

Quite la celda sin usar el 'indexPath' y en caso de que obtenga un elemento nulo, debe asignarlo manualmente.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"YourCellId"];
    if (!cell)
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"YourCellId"];

    // fill your cell object with useful stuff :)

    return cell;
}

Intentar usar self.tableView para quitar la cola de la celda puede causar bloqueos cuando tiene una lista principal seccionada y una lista de búsqueda simple. En cambio, este código funciona en cualquier situación.

Sandro Cavazzoni
fuente
1
Esto no funcionará si su celda está personalizada en el Storyboard y necesita ser instanciada desde ella y no su clase.
Lee Probert
3

Cuando tuve este problema, la solución fue reemplazar tableViewdequeueReusableCellWithIdentifier: @yourcell conself.tableView

glorio
fuente
2

También estoy trabajando en ese tutorial. El TableViewController predeterminado tiene "forIndexPath" y en su ejemplo no existe. Una vez que lo eliminé, la búsqueda funciona.

//Default code
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

//Replace with
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
sonic babbler
fuente
1
Desafortunadamente, esto no funciona con las celdas de prototipo dinámico de Storyboard.
Ortwin Gentz
2

para swift 3 solo necesita agregar uno mismo:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cell = self.tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! YourCell

    ...
}
Eugene Gordin
fuente