Assertionsfehler bei Verwendung von UISearchDisplayController in UITableViewController

80

Ich habe versucht, einem TableViewController in meiner App einfache Suchfunktionen hinzuzufügen. Ich folgte Ray Wenderlichs Tutorial. Ich habe eine Tabellenansicht mit einigen Daten, ich habe die Suchleiste + den Display-Controller im Storyboard hinzugefügt und dann habe ich diesen Code:

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

Das Standardmaterial, aber wenn ich Text in die Suchleiste eingebe, stürzt es jedes Mal mit diesem Fehler ab:

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'

Ich verstehe, dass sich in iOS 6 das Handhabungs- und Warteschlangensystem für Zellen geändert hat und dass bei der Suche eine andere Tabellenansicht verwendet wird. Daher dachte ich, dass das Problem darin bestand, dass die Suchtabellenansicht mit den gefilterten Ergebnissen nichts über die Zelle wusste dies in meiner viewDidLoad:

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

Und voila! Es hat funktioniert ... Nur beim ersten Suchen. Wenn Sie zu den ursprünglichen Ergebnissen zurückkehren und erneut suchen, stürzt die App mit demselben Fehler ab. Ich dachte darüber nach, vielleicht alles hinzuzufügen

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

Zeug zur cellForRow-Methode, aber widerspricht das nicht dem ganzen Zweck, die dequeueReusableCellWithIdentifier: forIndexPath: -Methode zu haben? Wie auch immer, ich bin verloren. Was vermisse ich? Hilfe bitte. Vielen Dank im Voraus für all Ihre Zeit (:

Alex.

acib708
quelle

Antworten:

194

Versuchen Sie, in dequeueReusableCellWithIdentifier self.tableView anstelle von tableView zu verwenden:

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

Dieser Code funktioniert ziemlich gut

Hinweis

Wenn Sie benutzerdefinierte Höhenzellen haben, verwenden Sie diese nicht

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

Verwenden Sie dies stattdessen

[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
FunkyKat
quelle
4
Würde das nicht einfach auf unbestimmte Zeit neue Zellen von einer Tabelle zur anderen instanziieren? Wenn es sich nach einer schlechten Idee anhört, Zellen von einem Tisch zu entfernen und sie einem anderen zu geben
Maltalef
1
Ich dachte, das würde auch funktionieren. Das tut es nicht. In meinem Fall führt der Versuch, die Zelle aus meiner Haupttabelle für die Suchtabelle zu entfernen, manchmal zu einem Absturz.
Lee Probert
1
Funktioniert nicht Versuchen Sie zu suchen, brechen Sie dann ab (beenden Sie den Overlay-Ergebnismodus) und suchen Sie dann erneut. Stürzt ab.
Daniel
8
Die vorgeschlagene Lösung wirft für mich immer noch eine Ausnahme auf. Allerdings scheint es zu funktionieren wie erwartet , wenn ich ersetzen dequeueReusableCellWithIdentifier:forIndexPath:durch dequeueReusableCellWithIdentifier:.
Jweyrich
3
Danke! self.tableview statt tableview war das Problem! Lebensretter!
Adamteale
14

Der Grund, warum es beim ersten Start hervorragend funktioniert hat, dann aber abgestürzt ist, wenn Sie die Ergebnistabelle verlassen und erneut gesucht haben, liegt darin, dass der Search Display Controller UITableViewjedes Mal, wenn Sie in den Suchmodus wechseln, eine neue lädt .

Mit Suchmodus meine ich, Sie haben auf das Textfeld getippt und mit der Eingabe begonnen. Zu diesem Zeitpunkt wird eine Tabellenansicht generiert, um die Ergebnisse anzuzeigen. Sie verlassen diesen Modus, indem Sie auf die Schaltfläche Abbrechen klicken. Wenn Sie das zweite Mal auf das Textfeld tippen und erneut mit der Eingabe beginnen, wird der Suchmodus zum zweiten Mal aufgerufen.

Um den Absturz zu vermeiden, sollten Sie die Zellenklasse für die Tabellenansicht registrieren, die in der searchDisplayController:didLoadSearchResultsTableView:Delegate-Methode (von UISearchDisplayDelegate) anstelle der Controller- viewDidLoadMethode verwendet werden soll.

Wie folgt:

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

Das hat mich überrascht, weil unter iOS 7 ... die Tabellenansicht wiederverwendet wird. Sie können die Klasse also anmelden, viewDidLoadwenn Sie dies bevorzugen. Aus Gründen des Vermächtnisses behalte ich meine Registrierung in der von mir erwähnten Delegatenmethode.

Daniel
quelle
2
Leider funktioniert dies nicht mit dynamischen Storyboard-Prototypzellen.
Ortwin Gentz
Aber es funktioniert gut mit Zellen, die von der Feder registriert werden. Gute Problemumgehung.
Thomas Verbeek
12

Nach der Suche scheint 'tableView' der cellForRowAtIndexPath-Methode keine Instanz der von Ihnen definierten Tabelle zu sein. Sie können also eine Instanz einer Tabelle verwenden, die die Zelle definiert. Anstatt:

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

Verwenden:

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

(Verwenden Sie nicht die tableView der cellForRowAtIndexPath-Methode, sondern self.tableView .)

aqubi
quelle
2
+1. Benötigte self.tableView in meiner schnellen Implementierung dieses Konzepts.
Davidethell
3

Deaktivieren Sie die Zelle, ohne den 'indexPath' zu verwenden. Wenn Sie ein Null-Element erhalten, müssen Sie es manuell zuweisen.

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

Der Versuch, self.tableView zum Löschen der Zelle zu verwenden, kann zu Abstürzen führen, wenn Sie eine unterteilte Hauptliste und eine einfache Suchliste haben. Dieser Code funktioniert stattdessen in jeder Situation.

Sandro Cavazzoni
quelle
1
Dies funktioniert nicht, wenn Ihre Zelle im Storyboard benutzerdefiniert ist und von ihr und nicht von ihrer Klasse instanziiert werden muss.
Lee Probert
3

Als ich dieses Problem hatte, ersetzte die Lösung tableViewdequeueReusableCellWithIdentifier: @yourcell durchself.tableView

Glorio
quelle
2

Ich arbeite auch an diesem Tutorial. Der Standard-TableViewController hat "forIndexPath" und in seinem Beispiel existiert er nicht. Sobald ich es entfernt habe, funktioniert die Suche.

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

//Replace with
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Sonicbabbler
quelle
1
Leider funktioniert dies nicht mit dynamischen Storyboard-Prototypzellen.
Ortwin Gentz
2

Für Swift 3 müssen Sie nur sich selbst hinzufügen:

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

    ...
}
Eugene Gordin
quelle