6

Since iOS 8 I'm experiencing a weird issue with a table view/UISearchBar setup and wondered if others have experienced a similar issue or can point to what, if anything, I might be doing wrong. The broad situation:

  • I have a UITableViewController with a UISearchBar within it, set up in the app's Storyboard
  • the table view has a custom cell, again, set up in the Storyboard
  • selecting a table row triggers a segue to another view
  • performing a search, tapping a row from the search results to segue to the other view, then navigating back again, triggers various issues.

"The issues" are that if I implement cellForRowAtIndexPath as follows:

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

in other words by specifying the path to dequeueReusableCellWithIdentifier, then this results in a BAD_ACCESS or assertion failure in iOS 8 (but not iOS 7). Specifically, either an assertion failure or BAD_ACCESS occurs on calling dequeueReusableCellWithIdentifier under the circumstances mentioned above, i.e. when, with a search active, you segue from one of the cells in the results table to another view and then segue back again.

Now, I can stop the error occurring by just calling:

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

without passing in the indexPath. This then works without an error as such, but on segueing back to the table view with search results, a weird display issue occurs whereby layered underneath the search results, there appear to be the separators of a "ghost" table, almost as though the system is trying to render one table directly on top of another (but cellForRowAtIndexPath isn't being called for each table, only for the search results table as expected).

I get the same problem whether the segue is attached to the cell or table view controller (so in the latter case, I implement didSelectRowAtIndexPath to manually trigger the segue).

So: (a) can anyone point to something I might be doing wrong to cause these issues, or (b) point to a bare-bones working example of a table view controller with UISearchBar where the table cells segue to another view? I'm surprised I'm getting so many issues as implementing a searchable table with detail views must be a common, boring thing that people do all of the time, no?

Sample project exhibiting the iusse: http://www.javamex.com/DL/TableTest.zip

Neil Coffey
  • 21,615
  • 7
  • 62
  • 83
  • Can you attach a sample project for that? I can't reproduce it. – gabbler Nov 02 '14 at 07:21
  • Uploaded a sample: if you load up the app, search for .e.g "One", then click the info icon on the the row in the search results to go to the detail view, then go back, start deleting the search term "One" to enter something else, you'll get the assertion failure. – Neil Coffey Nov 03 '14 at 11:49
  • I can't reproduce the second issue. What I did was putting a condition `if (self.searchDisplayController.active) { cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell"]; } else cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath];` And it's working well – E-Riddie Nov 03 '14 at 12:08
  • I have Downloaded your Projects, And it seems that it is working OK! – Vishal Sharma Nov 05 '14 at 09:16

4 Answers4

4

I Downloaded your code and what i observed is if i change the

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 

method as follows it is working fine for me.

Solution1:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    if (tableView == self.searchDisplayController.searchResultsTableView) {
        return [searchResults count];

    } else {
        return [testData count];

    }

}

Please let me know if it works for you and what my suggestion is use "NSPredicate" to filter the array like as follows.

- (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    NSLog(@"Search text did change");

    NSPredicate *resultPredicate = [NSPredicate
                                    predicateWithFormat:@"SELF contains[cd] %@",
                                    searchText];

    searchResults = [testData filteredArrayUsingPredicate:resultPredicate];
}

Nlote: Take "searchResults" as NSArray not NSMutableArray.

The above solution is working for me Here is one more approach to solve the problem.

Solution2:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    if (tableView == self.searchDisplayController.searchResultsTableView) {
        return [searchResults count];

    } else {
        return [testData count];

    }

}

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    NSPredicate *resultPredicate = [NSPredicate
                                    predicateWithFormat:@"SELF contains[cd] %@",
                                    searchText];

    searchResults = [testData filteredArrayUsingPredicate:resultPredicate];
}

// I implemented The following delegate method.


-(BOOL)searchDisplayController:(UISearchDisplayController *)controller
shouldReloadTableForSearchString:(NSString *)searchString {

    [self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles]
     objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
    return YES;
}
Miknash
  • 7,888
  • 3
  • 34
  • 46
Logger
  • 1,274
  • 1
  • 14
  • 25
2

There's actually nothing wrong with using either method to dequeue your cell for the main tableView, although the indexPath variant seems to be Apple's preferred option these days.

For the searchResultsTableView however, avoid specifying the indexPath as it's not necessarily valid for your view controller's tableView. That is:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyTableViewCell *cell;
    if ([tableView isEqual:self.searchDisplayController.searchResultsTableView]) {
        cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell"];
    } else {
        cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath];
    }
    // configure the cell
}

For this to work correctly, you also need to amend your other UITableViewDataSource method. Instead of:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return (searchResults) ? (searchResults.count) : (testData.count);
}

Do:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if ([tableView isEqual:self.searchDisplayController.searchResultsTableView]) {
        return searchResults.count;
    }
    return testData.count;
}
followben
  • 9,067
  • 4
  • 40
  • 42
2

Actually there are two tableViews, UISearchResultsTableView for displaying the result, self.tableView for source data displaying.

When you search for "One", and clear all search text, there is only one visible cell left in self.tableView and no visible cell in UISearchResultsTableView. Actually at this time both tables should have no visible cells left. Then you search for "T", now there should be two cells in UISearchResultsTableView because "Two" and "Three" match "T", at this time, there is only one visible cell in self.tableView, which label text is "One",dequeueReusableCellWithIdentifier:forIndexPath works fine for row 0, for row 1, it crashes. I think the reason is that there is only one visible cell for self.tableView.

MyTableViewCell *cell;
if (tableView != self.tableView) {
    cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell"];
} else {
    cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath];
}

And in textDidChange, add this at the end to clear tableView when searchText is empty string.

if (searchText.length == 0) {
    [self.tableView reloadData];
}
gabbler
  • 13,626
  • 4
  • 32
  • 44
2

After trying several approaches, my advice is to avoid fighting against Apple API.

UISearchDisplayController manages it's own UITableView, in addition to having your primary table. The cell identifier in the filtered table doesn't match your primary table.

As detailed here: UISearchDisplayController and UITableView prototype cell crash


Thus the best approach is the one you've already discovered:

MyCell *cell = (MyCell *) [self.tableView dequeueReusableCellWithIdentifier:@"MyId"];

As for the "ghost" table's separators issue, which unfortunately I wasn't able to reproduce, I would deal with it in this fashion:

self.searchDisplayController.searchResultsTableView.separatorColor  = [UIColor clearColor];

Best of luck.

Community
  • 1
  • 1
carlodurso
  • 2,886
  • 4
  • 24
  • 37