Błąd asercji podczas używania UISearchDisplayController w UITableViewController

80

Próbowałem dodać prostą funkcję wyszukiwania do TableViewController w mojej aplikacji. Postępowałem zgodnie z poradnikiem Raya Wenderlicha. Mam tableView z danymi, dodałem pasek wyszukiwania + kontroler wyświetlacza w scenorysie, a potem mam ten kod:

#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;
    }

Standardowe rzeczy, ale kiedy wpisuję tekst w pasku wyszukiwania, za każdym razem ulega awarii z tym błędem:

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'

Rozumiem, że w iOS 6 zmienił się system obsługi i usuwania z kolejki komórek, a także, że wyszukiwanie używa innego tableView, więc pomyślałem, że problem polega na tym, że wyszukiwanie tableView z przefiltrowanymi wynikami nie wie o komórce, więc wstawiłem to moim zdaniem

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

I voila! Zadziałało ... Tylko przy pierwszym wyszukiwaniu. Jeśli wrócisz do oryginalnych wyników i ponownie rozpoczniesz wyszukiwanie, aplikacja ulegnie awarii z tym samym błędem. Pomyślałem o może dodaniu wszystkich

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

stuff do metody cellForRow, ale czy nie jest to sprzeczne z całym celem posiadania metody dequeueReusableCellWithIdentifier: forIndexPath:? W każdym razie zgubiłem się. czego mi brakuje? Prosimy o pomoc. Z góry dziękuję za poświęcony czas (:

Alex.

acib708
źródło

Odpowiedzi:

194

Spróbuj użyć self.tableView zamiast tableView w 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;
}

Ten kod działa całkiem nieźle

Uwaga

Jeśli masz komórki o niestandardowej wysokości, nie używaj

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

Użyj tego zamiast tego

[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
FunkyKat
źródło
4
czy nie utworzyłoby to w nieskończoność nowych komórek z jednej tabeli do drugiej? jeśli odkolejkowanie komórek z jednego stołu i przekazanie ich do innego brzmi jak zły pomysł
maltalef
1
Myślałem, że to też zadziała. Tak nie jest. W moim przypadku próba usunięcia z kolejki komórki z tabeli głównej do tabeli wyszukiwania czasami powoduje awarię.
Lee Probert
1
Nie działa. Spróbuj wyszukać, a następnie anuluj (wyjdź z trybu wyników nakładania), a następnie wyszukaj ponownie. Awarie.
Daniel,
8
Proponowane rozwiązanie nadal stanowi dla mnie wyjątek. Jednak wydaje się, że działa zgodnie z oczekiwaniami, jeśli zastąpić dequeueReusableCellWithIdentifier:forIndexPath:przez dequeueReusableCellWithIdentifier:.
jweyrich
3
Dziękuję Ci! Problem dotyczył self.tableview zamiast tableview! ratownik!
adamteale
14

Powodem, dla którego działało świetnie przy pierwszym uruchomieniu, ale potem uległo awarii, jeśli opuściłeś tabelę wyników i wróciłeś do kolejnego wyszukiwania, jest to, że kontroler wyświetlania wyszukiwania ładuje nowy za UITableViewkażdym razem, gdy wchodzisz w tryb wyszukiwania.

Mówiąc tryb wyszukiwania, mam na myśli, że dotknąłeś pola tekstowego i zacząłeś pisać, w którym momencie generowany jest widok tabeli w celu wyświetlenia wyników, wychodząc z tego trybu, który osiągnięto, naciskając przycisk anulowania. Gdy dotkniesz pola tekstowego po raz drugi i zaczniesz pisać ponownie - oznacza to wejście w „tryb wyszukiwania” po raz drugi.

Aby uniknąć awarii, należy zarejestrować klasę komórki dla widoku tabeli, która ma być używana w searchDisplayController:didLoadSearchResultsTableView:metodzie delegata (z UISearchDisplayDelegate) lub w viewDidLoadmetodzie kontrolerów .

Następująco:

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

Zaskoczyło mnie to, ponieważ na iOS 7 ... widok tabeli jest ponownie używany. Możesz więc zarejestrować zajęcia, viewDidLoadjeśli wolisz. Ze względu na dziedzictwo zachowam swoją rejestrację w metodzie delegowania, o której wspomniałem.

Daniel
źródło
2
Niestety nie działa to z dynamicznymi komórkami prototypów Storyboard.
Ortwin Gentz
Ale działa świetnie z komórkami zarejestrowanymi przez stalówkę. Dobre obejście.
Thomas Verbeek,
12

Po wyszukaniu „tableView” metody cellForRowAtIndexPath wydaje się nie być instancją zdefiniowanej tabeli. Możesz więc użyć wystąpienia tabeli, która definiuje komórkę. Zamiast:

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

Posługiwać się:

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

(Nie używaj tableView metody cellForRowAtIndexPath, użyj self.tableView ).

aqubi
źródło
2
+1. Potrzebny self.tableView w mojej szybkiej implementacji tej koncepcji.
davidethell
3

Zdejmij komórkę z kolejki bez użycia parametru „indexPath”, a jeśli uzyskasz element zerowy, musisz go przydzielić ręcznie.

- (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;
}

Próba użycia self.tableView do usunięcia z kolejki komórki może spowodować awarie, jeśli masz podzieloną listę główną i zwykłą listę wyszukiwania. Ten kod zamiast tego działa w każdej sytuacji.

Sandro Cavazzoni
źródło
1
To nie zadziała, jeśli komórka jest niestandardowa w Storyboard i musi zostać utworzona z niej, a nie z jej klasy.
Lee Probert
3

Kiedy miałem ten problem, rozwiązaniem była zamiana tableViewdequeueReusableCellWithIdentifier: @yourcell naself.tableView

glorio
źródło
2

Pracuję również nad tym samouczkiem. Domyślny TableViewController ma „forIndexPath” iw jego przykładzie nie istnieje. Po usunięciu tego wyszukiwanie działa.

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

//Replace with
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
sonicbabbler
źródło
1
Niestety nie działa to z dynamicznymi komórkami prototypów Storyboard.
Ortwin Gentz
2

dla Swift 3 wystarczy dodać siebie:

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

    ...
}
Eugene Gordin
źródło