0
votes

I have a UISearchController in a UITableViewController. I want to populate the search bar with the last searched text every time the table view is shown. For this, I have added code in viewDidAppear to retrieve the last search string and set the UISearchController active.

Whenever I do not enter any search text, everything works fine.

After I enter search text, the search works fine the first time. However, when I re-open this view, and populate UISearchController with the last search term, AND if the new list now returns empty result (It's possible because my list in the tableview is dynamic), then the app crashes in cellForRowAtIndexPath with attempt to access a row into an empty search results array.

I put NSLogs in many places, and discovered a strange thing. Even if UISearchController is active, the corresponding condition in numberOfRowsInSection returns FALSE, thereby returning the count of items in the original items list rather than the searched items list (which is now 0).

It calls numOfRowsInSection, when search controller is active, instead of going in "if ([_searchController isActive])" clause of that function, it incorrectly goes in "else" clause, gets the original lists' count (non-searched), which is 0, and then calls cellForRowAtIndexPath with indexPath.row = 0. Strangely, in cellForRowAtIndexPath, it indeed goes correctly inside "if (_searchController.active)" clause. Evidently there is some race condition or I am missing something.

I am very confused why this is happening. Would appreciate any help.

@interface TaalListViewController ()

@property (strong, nonatomic) NSArray *m_searchedItemList;
@property NSArray *m_itemList;
@property (retain, nonatomic) UISearchController *searchController;

@end

@implementation TaalListViewController

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    @try {
        [super viewDidLoad];

        // Uncomment the following line to preserve selection between presentations.
        // self.clearsSelectionOnViewWillAppear = NO;

        // This is a global function declared and returns the items list. This works fine.
        _m_itemList = [Globals getItemsList];

        // Search controller
        _m_searchedItemList = [_m_itemList mutableCopy];

        self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
        self.searchController.searchBar.delegate = self;
        self.searchController.searchResultsUpdater = self;
        [self.searchController.searchBar sizeToFit];
        self.searchController.dimsBackgroundDuringPresentation = NO;
        self.definesPresentationContext = YES;
        self.tableView.tableHeaderView = self.searchController.searchBar;
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception in viewDidLoad");
        NSLog(@"CRASH: %@", exception);
        NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
    }
}


- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Get the last searched text and automatically set it in the search bar
    NSString *str = [Globals getLastSearchedText];
    if ((str != nil) && (!(str.length == 0)))
    {
        NSLog(@"Setting seach bar active automatically");
        self.searchController.searchBar.text = str;
        self.searchController.active = YES;
        // [self.searchController.searchBar becomeFirstResponder];
    }
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    @try {
        // Return the number of rows in the section.
        if ([self.searchController isActive])
        {
            NSLog(@"Number of searched item rows = %lu", (unsigned long)_m_searchedItemList.count);
            return _m_searchedItemList.count;
        }
        else
        {
            NSLog(@"Number of regular rows = %lu", (unsigned long)_m_itemList.count);
            return _m_itemList.count;
        }
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception in numberOfRowsInSection");
        NSLog(@"CRASH: %@", exception);
        NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    @try {
        static NSString *CellIdentifier = @"ListTableCell";

        ListTableViewCell *cell = (ListTableViewCell *) [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

        // Configure the cell...
        if (cell == nil)
        {
            cell = [[ListTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        }

        DisplayListItem *item = nil;

        if (self.searchController.active)
        {
            NSLog(@"Row %ld of search list", (long)indexPath.row);
            item = (DisplayListItem *) [_m_searchedItemList objectAtIndex:indexPath.row];
        }
        else
        {
            NSLog(@"Row %ld of regular list", (long)indexPath.row);
            item = (DisplayListItem *) [_m_itemList objectAtIndex:indexPath.row];
        }

        // Set the cell here and return
        // ...
        // Omitted code
        // ...

        return cell;
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception in cellForRowAtIndexPath");
        NSLog(@"CRASH: %@", exception);
        NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
    }
}

#pragma mark - UISearchResultsUpdating

-(void)updateSearchResultsForSearchController:(UISearchController *)searchController {

    NSString *searchString = [self.searchController.searchBar text];

    NSLog(@"updateSearchResultsForSearchController called, searchString = %@", searchString);

    _m_searchedItemList = [_m_itemList mutableCopy];

    if (searchString.length != 0)
    {
        [self filterContentForSearchText:searchString];
    }

    [self.tableView reloadData];
}

#pragma mark - UISearchBarDelegate

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
    NSLog(@"searchBarSearchButtonClicked called");
    [searchBar resignFirstResponder];
}

- (void)filterContentForSearchText:(NSString*)searchText
{
    NSLog(@"filterContentForSearchText called");
    @try {

        NSMutableArray *subPredicates = [NSMutableArray arrayWithCapacity:searchText.count];
        for (NSString *searchStr in searchText)
        {
            [subPredicates addObject:[NSPredicate predicateWithFormat:@"m_name contains[c] %@", searchStr]];
        }
        NSPredicate *resultPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
        _m_searchedItemList = [_m_itemList filteredArrayUsingPredicate:resultPredicate];

        [Globals setLastSearchedText:searchText];

        NSLog(@"filterContentForSearchText finished, searched count = %lu", _m_searchedItemList.count);
    }
    @catch (NSException *exception) {
        NSLog(@"Caught exception in filterContentForSearchText");
        NSLog(@"CRASH: %@", exception);
        NSLog(@"Stack Trace: %@", [exception callStackSymbols]);
    }
}

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    [Globals setLastSearchedText:searchText];
}
@end

Here is the output of the NSLogs in debugger.

2016-10-04 22:45:46.364927 AppName[1387:639346] Setting seach bar active automatically
2016-10-04 22:45:46.365378 AppName[1387:639346] updateSearchResultsForSearchController called, searchString = Kay
2016-10-04 22:45:46.365470 AppName[1387:639346] filterContentForSearchText called
2016-10-04 22:45:46.366522 AppName[1387:639346] filterContentForSearchText finished, searched count = 0
2016-10-04 22:45:46.366825 AppName[1387:639346] Number of regular rows = 215
2016-10-04 22:45:46.367319 AppName[1387:639346] updateSearchResultsForSearchController called, searchString = Kay
2016-10-04 22:45:46.367407 AppName[1387:639346] filterContentForSearchText called
2016-10-04 22:45:46.368190 AppName[1387:639346] filterContentForSearchText finished, searched count = 0
2016-10-04 22:45:46.368264 AppName[1387:639346] Number of regular rows = 215
2016-10-04 22:45:46.371081 AppName[1387:639346] Row 0 of search list
2016-10-04 22:45:46.373274 AppName[1387:639346] Caught exception in cellForRowAtIndexPath
2016-10-04 22:45:46.373383 AppName[1387:639346] CRASH: *** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray
2016-10-04 22:45:46.382029 AppName[1387:639346] Stack Trace: (
    0   CoreFoundation                      0x0000000192d981d8 <redacted> + 148
    1   libobjc.A.dylib                     0x00000001917d055c objc_exception_throw + 56
    2   CoreFoundation                      0x0000000192d033dc <redacted> + 0
    3   AppName                             0x00000001001269bc -[ListViewController tableView:cellForRowAtIndexPath:] + 512
    4   UIKit                               0x0000000198f243d4 <redacted> + 716
    5   UIKit                               0x0000000198f24604 <redacted> + 80
    6   UIKit                               0x0000000198f11bac <redacted> + 2304
    7   UIKit                               0x0000000198f29668 <redacted> + 116
    8   UIKit                               0x0000000198cc5b14 <redacted> + 176
    9   UIKit                               0x0000000198bde54c <redacted> + 1196
    10  QuartzCore                          0x00000001960a640c <redacted> + 148
    11  QuartzCore                          0x000000019609b0e8 <redacted> + 292
    12  QuartzCore
2016-10-04 22:45:46.382306 AppName[1387:639346] -[_UITableViewReorderingSupport _needsSetup]: unrecognized selector sent to instance 0x170290e00
2016-10-04 22:45:46.382714 AppName[1387:639346] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_UITableViewReorderingSupport _needsSetup]: unrecognized selector sent to instance 0x170290e00'
*** First throw call stack:
(0x192d981c0 0x1917d055c 0x192d9f278 0x192d9c278 0x192c9659c 0x198d22c6c 0x198f24440 0x198f24604 0x198f11bac 0x198f29668 0x198cc5b14 0x198bde54c 0x1960a640c 0x19609b0e8 0x19609afa8 0x196017c64 0x19603f0d0 0x19603faf0 0x192d457dc 0x192d4340c 0x192d4389c 0x192c72048 0x1946f5198 0x198c4c628 0x198c47360 0x10015aaa8 0x191c545b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
1
Intialize arrays before using.Bista
I have already initialized the arrays: // This is a global function declared and returns the items list. This works fine. _m_itemList = [Globals getItemsList]; // Search controller _m_searchedItemList = [_m_itemList mutableCopy]; This code is already initializing in viewDidLoad.Abhijit
Saw this behaviour, when leaving the searchmode while scrolling… In numberOfRows the searchController isActive was YES, when doing cellForRowAtIndexPath (because of scrolling), it was NO…Tobias

1 Answers

0
votes
2016-10-04 22:45:46.373274 AppName[1387:639346] Caught exception in cellForRowAtIndexPath
2016-10-04 22:45:46.373383 AppName[1387:639346] CRASH: *** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray

Says it right there! In cellForRowAtIndexPath you are trying to access element 0 of an empty NSArray

Don't do that and it won't crash!