I am facing a problem in implementing the search functionality in Table Views... Here is the code i am implementing to search...
- (void) searchTableView {
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
searchArray = [data boats];
for(Boat *boat in searchArray)
{
NSRange titleResultsRange = [[boat boatName] rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
[copyListOfItems addObject:boat];
}
[searchArray release];
searchArray = nil;
}
When the search bar starts getting edited, i call this...
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
//Remove all objects first.
[copyListOfItems removeAllObjects];
if([searchText length] > 0) {
[ovController.view removeFromSuperview];
searching = YES;
letUserSelectRow = YES;
self.tableView.scrollEnabled = YES;
[self searchTableView];
}
else {
[self.tableView insertSubview:ovController.view aboveSubview:self.parentViewController.view];
searching = NO;
letUserSelectRow = NO;
self.tableView.scrollEnabled = NO;
}
[self.tableView reloadData];
}
and i assign the value to table row using...
if(searching)
{
Boat *copyboat = [copyListOfItems objectAtIndex:[indexPath row]];
cell.textLabel.text = [copyboat boatName];
NSLog(#"%#", [copyboat boatName]);
}
else {
Boat *fullboat =[data.boats objectAtIndex:[indexPath row]];
cell.textLabel.text =[fullboat boatName];
}
But when i start typing the values in search bar, the app is crashing. I get different errors when i type different alphabets. I get errors like.
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[NSMutableArray objectAtIndex:]: index 6 beyond bounds [0 .. 5]'
or
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[NSMutableArray objectAtIndex:]: index 0 beyond bounds for empty array'
Sometime its also showing EXEC_BAD_ACCESS also... I am screwed right now. Can anyone please tell me what the error exactly is...???
When you create the subset of boat names, do you change the value of what you return in numberOfRowsInSection? It looks like the table is reloading and trying to find data for 7 rows, but you now only have data for six. Something like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (searching) {
return [copyListOfItems count];
} else {
return [data count];
}
}
Related
I have a UISearchDisplayController on a UITableView. The UITableView grabs JSON data from a server, breaks it down, and an object class takes that data and creates objects. It adds the Objects to a Mutable array to then use as the datasource for the table.
There is also another Mutable array used as the 'filtered' objects.
It's declared like so:
self.filteredItems = [NSMutableArray arrayWithCapacity:[self.items count]];
When I try to search the table I get this error:
Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
This is the code I'm using to do the filtering, which I found after going through a bunch of tutorials, I can't use NSPredicate because it seems the return function returns an NSArray, and I have an NSMutableArray, which I can not change because i'm not declaring the objects statically.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredItems removeAllObjects];
for (Update *update in items)
{
if ([scope isEqualToString:#"Serial Number"]) {
NSComparisonResult result = [[update Serial_Number] compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame){
[filteredItems addObject:update];
}
} else if ([scope isEqualToString:#"Ticket Number"]) {
NSComparisonResult result = [[update Ticket_Number] compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame){
[filteredItems addObject:update];
}
} else if ([scope isEqualToString:#"Customer"]) {
NSComparisonResult result = [[update Customer] compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame){
[filteredItems addObject:update];
}
}
}
}
Not too sure what I'm doing wrong, I've never tried to implement this control before.
I actually ran break points, I can see that the objects are getting added to the filtered array, but then suddenly it crashes.
If you need any other part of the code to help me please let me know.
Thank you!
Edit:
Here's the delegate methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (updatesTableView == self.searchDisplayController.searchResultsTableView) {
return [self.filteredItems count];
} else {
return [self.items count];
}
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
Update *update = nil;
if (tableView == self.searchDisplayController.searchResultsTableView) {
update = [filteredItems objectAtIndex:indexPath.row];
} else {
// Configure the cell...
update = [items objectAtIndex:indexPath.row];
}
[[cell textLabel] setText:[update Serial_Number]];
[[cell detailTextLabel] setText:[NSString stringWithFormat:#"Customer: %#", [update Customer]]];
cell.textLabel.adjustsFontSizeToFitWidth = YES;
cell.detailTextLabel.adjustsFontSizeToFitWidth = YES;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
The normal table displays just fine as well as their detail views.
In your delegate methods you have in 'numberOfRows':
if (updatesTableView == self.searchDisplayController.searchResultsTableView) {
then in 'cellForRowAtIndexPath':
if (tableView == self.searchDisplayController.searchResultsTableView) {
You're comparing different tables for determining the number of rows and then which array to get the data from.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (tableView == self.searchDisplayController.searchResultsTableView) {
return [self.filteredItems count];
} else {
return [self.items count];
}
}
Im developing an app, need to load some data in background , then show the data using UITableView.
Here are some codes,
loading data in background:
- (void)loadRelatedItems
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSString *mediaType in allMediaTypes)
{
[self performSelector:#selector(loadRelatedItems:) withObject:mediaType];
}
NSString *notificationName = [CommonFunction allRelatedItemsLoadedNotificationName];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:nil];
[pool release];
}
- (void)loadRelatedItems:(NSString *)mediaType
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (NSString *keyword in _keywords)
{
NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:#"%#&mediaType=%#&keyword=%#", API, mediaType, keyword]];
NSMutableArray *items = [CommonFunctions arrayFromURL:URL];
if ([items count] == 0) continue;
NSString *notificationName = [CommonFunction partialRelatedItemsLoadedNotificationName];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:items, #"items", mediaType, #"mediaType", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:dic];
}
[pool release];
}
showing the data in UITableView:
- (void)didFinishLoadPartialRelatedItems:(id)sender
{
NSDictionary *dic = [sender userInfo];
NSString *mediaTypeString = [dic objectForKey:#"mediaType"];
NSMutableArray *items = [dic objectForKey:#"items"];
dispatch_async(dispatch_get_main_queue(), ^{
if ([_relatedItems count] == 0)
{
[_relatedItems setObject:items forKey:mediaTypeString];
[_tableView reloadData];
}
else
{
NSMutableArray *mediaTypeItems = [_relatedItems objectForKey:mediaTypeString];
if (mediaTypeItems)
{
// section exist
NSInteger section =[[[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)] indexOfObject:mediaTypeString];
NSMutableArray *indexPaths = [NSMutableArray array];
for (NSMutableDictionary *item in items)
{
[mediaTypeItems addObject:item];
NSInteger newRow = [mediaTypeItems indexOfObject:item];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:newRow inSection:section];
[indexPaths addObject:newIndexPath];
}
[_tableView beginUpdates];
[_tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[_tableView endUpdates];
}
else
{
// new section
[_relatedItems setObject:items forKey:mediaTypeString];
NSInteger section =[[[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)] indexOfObject:mediaTypeString];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[_tableView beginUpdates];
[_tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
[_tableView endUpdates];
}
}
});
}
#pragma mark -
#pragma mark Table Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ([_relatedItems count] == 0) {
return 1;
} else {
return [_relatedItems count];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *allTitles = [[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)];
NSString *title = [allTitles objectAtIndex:section];
NSDictionary *allMediaTypeDisplayNames = [CommonFunction allMediaTypeDisplayNames];
return [allMediaTypeDisplayNames objectForKey:title];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([_relatedItems count] == 0) {
return 0;
}
NSArray *allTitles = [[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)];
NSString *title = [allTitles objectAtIndex:section];
NSInteger rowsCount = [[_relatedItems objectForKey:title] count];
return rowsCount;
}
I'm very confused that it works fine some times, but some times it crashed with message:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1030
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (0) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).
What's the problem? please help.
Please make sure after updating, your number of sections should be equal to number of sections before the update.
As per your code :
the number of sections are defined as like this:
if ([_relatedItems count] == 0) {
return 1;
} else {
return [_relatedItems count];
}
and in this case you are creating new section right ?
else { // new section
[_relatedItems setObject:items forKey:mediaTypeString];
NSInteger section =[[[_relatedItems allKeys] sortedArrayUsingSelector:#selector(mediaTypeCompare:)] indexOfObject:mediaTypeString];
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[_tableView beginUpdates];
[_tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationNone];
[_tableView endUpdates];
}
If you are creating new section then your [_relatedItems count] is increasing. So, please make sure after the inserting also your count should be same.
Right ?
Try This :
if ([_relatedItems count] == 0) {
return 1;
} else {
if([_relatedItems count]>previousCount)
return [_relatedItems count];
return previousCount;
}
when ever you are making any updates to the [_relatedItems]; then change update your previousCount also.. this will be solved
I think your problem is with the number of rows method in the datasource just do one thing define int noOfRows in your .h file .assign your table view array count with the noOfRows. noOfRows=[yourtableArray count];
then return noOfRows from table views numberOfRowsInSection method.
Add 1 to noOfRows if you insert row in the table.
Make noOfRows-- when you delete row from the table
You will not get this exception .Update your array as per your requirement.
I have a Navigation Controller that contains a uitableview when I press on a row it pops a new view controller on the stack, which is used to display detail information in the detail view it makes a request from the server to get some response information then once the information is returned I use insertRowsAtIndexPaths: to display the information that is returned from the server.
This all works fine the first time, then when i press the back button and select a new row or the same row for viewing the detailed information once I the insertRowsAtIndexPaths: is called i get the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Here is the code for pushing the view on the stack:
VideoDetailViewController_iPhone *nextView = [[VideoDetailViewController_iPhone alloc] initWithNibName:#"VideoDetailViewController_iPhone" bundle:nil withVideo:rowData];
nextView.navController = navController;
[navController pushViewController:nextView animated:YES];
[nextView release];
Here is the code is executed once the information is returned from the server
- (void)fetchVideoDetail:(NSNotification *)notification {
hasLoadedResponses = YES;
NSArray *obj = (NSArray *)[notification object];
responses = [[obj valueForKey:#"responses"] mutableCopy];
//NSLog(#"RESPONSES: %#", responses);
if ([responses count] == 0) {
[tblView reloadData];
return;
}
NSMutableArray *indexes = [[NSMutableArray alloc] init];
int i = 0;
for (NSArray *x in responses) {
if (i > 0) {
//The reason for skipping the first one is because we will change that row once the table refreshes we just need to insert any rows after the first one.
[indexes addObject:[NSIndexPath indexPathForRow:i inSection:1]];
}
i++;
}
//NSLog(#"indexCount: %i", [indexes count]);
[tblView beginUpdates];
[tblView insertRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationBottom];
[tblView endUpdates];
//[tblView reloadData];
}
Here is the tableView methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (section == 0) {
return 1;
} else {
if ([responses count] == 0) {
NSLog(#"numberofrowsinsection: 1");
return 1;
} else {
NSLog(#"numberofrowsinsection: %i", [responses count]);
return [responses count];
}
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
VideoCell *cell = (VideoCell *)[tableView dequeueReusableCellWithIdentifier:CellClassName];
if (cell == nil) {
NSArray *topLevelItems = [cellLoader instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
}
if (indexPath.section == 0) {
cell.lblTitle.text = [data title];
cell.lblDescription.text = [data videoDescription];
} else {
if ([responses count] == 0) {
if (!hasLoadedResponses) {
cell.lblTitle.text = #"";
cell.lblDescription.text = #"";
} else {
//Responses have been loaded
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.lblTitle.text = #"No responses to this video";
cell.lblDescription.text = #"Be the first to respond by selecting the \"Set as Destination\" button above";
}
} else {
//Display the response information
cell.lblTitle.text = [[responses objectAtIndex:indexPath.row] valueForKey:#"title"];
cell.lblDescription.text = [[responses objectAtIndex:indexPath.row] valueForKey:#"description"];
}
}
return cell;
}
Your datasource and number of rows are out of sync. When you insert the row, you have to increase the number of rows in the section at the same time. In this case you would have to increase the count of your array responses, which you are using in your numberOfRowsInSection method.
I am implementing a search bar controller to search a table view. The below method code which performs the search is crashing with the error "-[__NSArrayM rangeOfString:options:]: unrecognized selector sent to instance 0x65558e0'
The locationInfo array is an array containing 26 arrays, each of which contains a number of objects made up of strings.
Can anyone suggest why the code is crashing ?
Thank you.
- (void)handleSearchForTerm:(NSString *)searchTerm
{
[self setSavedSearchTerm:searchTerm];
if ([self searchResults] == nil)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[self setSearchResults:array];
[array release], array = nil;
}
[[self searchResults] removeAllObjects];
if ([[self savedSearchTerm] length] != 0)
{
for (NSString *currentString in [self locationInfo])
{
if ([currentString rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentString];
}
}
}
}
As you stated in question that "locationInfo" is array containing 26 arrays,
so,
currentString in [self locationInfo] will return an array only so try to write something like below :
for (NSArray *currentArray in [self locationInfo])
{
for (NSString *currentString in currentArray)
{
if ([currentString rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentString];
}
}
}
or something like this
Based on the error you're getting, it seems [self locationInfo] returns an array (NSArray) and not a string (NSString) as you're expecting.
I'm using an NSFetchedResultsController to display items in my table view:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [[self.fetchedResultsController fetchedObjects] count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TagCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];;
}
Tag *tag = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = tag.name;
return cell;
}
However, this code breaks at this line:
Tag *tag = [self.fetchedResultsController objectAtIndexPath:indexPath];
With this message:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)'
I've NSLogged [self.fetchedResultsController fetchedObjects] and I can confirm that there are indeed Tag objects. If I replace that above line with this everything works as expected:
Tag *tag = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
I NSLogged indexPath and the indexes are {0, 0} (section 0 row 0) so I know it isn't an issue with the section. I'm extremely confused as to why this is happening because theoretically, those two pieces of code do the same thing. Any help is appreciated.
Thanks
UPDATES:
id section = [[[self fetchedResultsController] sections] objectAtIndex:[indexPath section]];
NSLog(#"Section %#", section); <-- returns a valid section
This code results in the same exception: Tag *tag = [[section objects] objectAtIndex:[indexPath row];
If I NSLog [section objects] it returns an empty array. I'm not sure why [fetchedResultsController fetchedObjects] returns an array with the right objects, and [section objects] returns nothing. Does this mean that the objects I'm creating have no section? Here's the code that I use to add new objects:
- (void)newTagWithName:(NSString *)name
{
NSIndexPath *currentSelection = [self.tableView indexPathForSelectedRow];
if (currentSelection != nil) {
[self.tableView deselectRowAtIndexPath:currentSelection animated:NO];
}
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Tag *newTag = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:self.managedObjectContext];
// Configure new tag
newTag.name = name;
[self saveContext];
NSIndexPath *rowPath = [self.fetchedResultsController indexPathForObject:newTag];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:rowPath] withRowAnimation:UITableViewRowAnimationTop];
[self.tableView selectRowAtIndexPath:rowPath animated:YES scrollPosition:UITableViewScrollPositionTop];
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
}
And here's my saveContext method:
- (void)saveContext
{
// Save changes
NSError *error;
BOOL success = [self.managedObjectContext save:&error];
if (!success)
{
UIAlertView *errorAlert = [[[UIAlertView alloc] initWithTitle:#"Error encountered while saving." message:nil delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil] autorelease];
[errorAlert show];
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
Am I doing something wrong here?
I ran into the same problem. In my case, it seems to be a corrupt cache file. Try renaming your cache, or calling deleteCacheWithName:.
When you initialized your fetch request controller, you gave it a sectionNameKeyPath: even though your data has no sections. Then you hard coded your tables section number to 1.
The fetch request controller is trying to return an object at section index zero in a data structure that has no sections at all.
I reproduced your error in the default navigation template app by changing the sectionNameKeyPath from nil to the name of the entity's sole attribute.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"timeStamp" cacheName:#"Root"];
... and then changed
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// return [[fetchedResultsController sections] count];
return 1;
}
and I get:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSCFArray objectAtIndex:]: index (0) beyond bounds (0)'
Set the sectionNameKeyPath:nil and that should fix your problem.
Are you compiling against, targeting or testing on 3.0?
What do you get back from the following code:
id section = [[[self fetchedResultsController] sections] objectAtIndex:[indexPath section]];
NSLog(#"Section %#", section);
Are you getting a valid section back?
If so, try adding the following line:
Tag *tag = [[section objects] objectAtIndex:[indexPath row];
If that produces an error then I suspect the issue is in your -tableView: numberOfRowsInSection: and that it may be giving back the wrong number of rows to the UITableView. Can you edit your question and post the code for that method as well?
Update
On further review I see where TechZen was pointing. You are telling the table view that you have one section when you may not. Further you are telling it how many objects your entire NSFetchedResultsController has when you should be answering it a little more succinctly.
Here is a slight change to your code.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
Make those changes and see what impact that has.
Question
What other UITableViewDataSource methods have you implemented? Sounds like there may be at least one more lingering error in your implementation.
got this error last night and it drove me crazy. I found that if you use the storyboard to specify the number of rows and sections, it needs to match the number you specify in your data source methods. To completely fix the error, you can set everything to 0 in the attributes inspector and just do it all programmatically, or just make sure that both data source methods and attributes inspector reflect the same amount of rows and sections.
^^