IOS Searchbar returns wrong data - ios

I am going crazy trying to get my IOS Searchbar working for the Iphone. I access data from a remote server and populate a content file. I then do a filter which creates a filtered content file. I then do a [self.tableView reloadData()]. It works fine the first time around. Then I change my scope and do another fetch of data from my server and filter it and do another reload. However, the second time the table shows the first 9 items from the previous display rather than the new 9 items from the filtered file. I console display the file count in the filtered file which in this case is 9 in the tableView numberOfRowsInSection: I also display each item going through the cellForRowAtIndexPath. In the cellForRowAtIndexPath I am displaying the correct 9 unfiltered items but they do not show up on the table! The table shows the first 9 items from the old display instead.
My question is doesn't the new data display on the table instead of the old data even though the count is correct? Why am I displaying the correct items on the console but yet the display shows items from the old display. WHat do I need to do to make the new data appear? I know this is pretty hard to comprehend but I am listing some of my code below in hopes that someone can give me a clue on why the table view is not being updated with the latest data.
// This is where I get data back from the server.
self.listContent = [[NSArray alloc] init];
if(_scopeIndex == 0)
self.listContent = [jsonObject objectForKey:#"burials"];
else
if(_scopeIndex == 1)
self.listContent = [jsonObject objectForKey:#"obits"];
else
self.listContent = [jsonObject objectForKey:#"photos"];
if(self.listContent > 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
[self.tableView reloadData];
});
}
Below is where the data is filtered. In this case the unfiltered and filtered file are the same.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredListContent removeAllObjects];// First clear the filtered array.
/*
Search the listContent for names that match and add to the filtered array.
*/
for (int i = 0; i < [self.listContent count]; i++)
{
NSComparisonResult result = [self.listContent[i][#"LastName"] compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.filteredListContent addObject:self.listContent[i]];
}
// }
}
}
This is where I get the table count of filtered items.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
NSLog(#"Filtered Name count = %i", [self.listContent count]);
return [self.filteredListContent count];
}
else
{
NSLog(#"Name count = %i", [self.listContent count]);
return [self.listContent count];
}
}
ANd this is where I update the cells in my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *kCellID = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
if(indexPath.row > [self.filteredListContent count] - 1)
return cell;
NSDictionary *burial = [self.filteredListContent objectAtIndex:indexPath.row];
NSString *lastname = burial[#"LastName"];
NSString *firstname = burial[#"FirstName"];
NSString *burialname = [NSString stringWithFormat: #"%#, %#", lastname, firstname];
cell.textLabel.text = burialname;
NSLog(#"Cell name= %# index path=%i", cell.textLabel.text, indexPath.row);
return cell;
}

I changed my logic to go to the server one time to get my content and this time included scope indicators in my content table. This enables me to process scope filters without having to go back to the server for data for a specific scope. Doing this resulted in proper view tables being displayed when changing scope. I would not recommend going to the server ascynchronously whenever the scope changes on the search as it really screws up the view table.

Some things to try:
Maybe you're reloading your tableView too soon, or
Your cellForRowAtIndexPath needs to distinguish between the table views (just as your numberOfRowsInSection does), or
Maybe you don't have all of your delegates set up correctly. The searchBar is used with a UISearchDisplayController, which has 2 delegates: searchResultsDataSource and searchResultsDelegate. Make sure those are set to self (or whatever class handles these).

How are you using searchbar? Logic should be that you have tableview with all data and the you use search bar to filter out matching results and add them to search data array and while searchbar is active you display search data array.
Is
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
used somewhere?
Added following
If you have this function set up you can update search array and reload it using something like
if (self.searchDisplayController.searchResultsTableView != nil)
{
//Updates searchData array
[self searchDisplayController:self.searchDisplayController shouldReloadTableForSearchString:searchBar1.text];
//Updates display
[self.searchDisplayController.searchResultsTableView reloadData];
}
else
{
[_channelsTableView reloadData];
}
It is calling searchDisplayController with current search bar text. When new tableview data comes from server you can activate searchDisplayController to refresh search results array and then it is possible to refresh the display.

Related

Blank rows on 2 TableViewController with data from one Viewcontroller in the other viewcontroller

I have 2 UITableViewControllers in my project.
The problem I am having is that I am getting blank cell entries in the tableView opposite to the tableView where the data is entered.
I can't seem to figure out why this is the case.
It's creating blank rows in this tableView even though the information is from the other UITableViewController.
Here's the main tableView part from the one of the 2 UITableViewControllers:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.diaryoptions count]);
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
return [self.diaryoptions count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
Data2 *diary = [self.diaryoptions objectAtIndex:indexPath.row];
cell.textLabel.text = diary.diaryname;
cell.detailTextLabel.text = diary.diaryWeight;
return cell;
}
And from other UITableViewController:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.addedSpaceObjects count]);
// Return the number of sections.
if ([self.addedSpaceObjects count]) {
return 2;
}
else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
if (section == 1) {
return [self.addedSpaceObjects count];
}
else {
return [self.recipes count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Josh";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
else {
// Configure the cell...
Data *recipe = [self.recipes objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
return cell;
}
Here is the full project on GitHub. https://github.com/josher32/Plant-Diet
Appreciate any help anyone can offer!
Ok, so I checked out the app and I'll try my best to explain the problem as precisely as I can to cover it adequately.
Firstly, the classes in question are:
RecipesTableTableViewController
AddRecipeViewController
Data
DiaryTableViewController
AddDiaryViewController
Data2
Secondly, we'll need to look into your
#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
AddRecipeViewController
So... AddRecipeViewController basically creates a Data object that is kept in an array and eventually stored in NSUserDefaults under the key name Added Space Objects Array.
Great!! So you now have got recipe related stuff in some Data object.
AddDiaryViewController
Same thing here.
AddDiaryViewController creates a Data2 object that is eventually stored in NSUserDefaults under the same key name Added Space Objects Array.
But before storing this, you're taking the old value of the key Added Space Objects Array, which is an array, and adding a new object to it before placing it back into NSUserDefaults.
But now... this array will now have a combination of Data as well as Data2 objects!
RecipesTableTableViewController
When we come here, things get real.
- (void)viewDidLoad
{
//...
NSArray *myRecipeAsPropertyLists = [[NSUserDefaults standardUserDefaults] arrayForKey:ADDED_SPACE_OBJECTS_KEY];
for (NSDictionary *dictionary in myRecipeAsPropertyLists) {
Data *spaceObject = [self spaceObjectForDictionary:dictionary];
[self.addedSpaceObjects addObject:spaceObject];
}
}
Since we already realized that self.addedSpaceObjects can contain Data as well as Data2 objects, in the case whendictionary is containing stuff specific to type Data2, spaceObjectForDictionary will not be able to translate it properly to the required Data object.
We're expecting name, title, ingredients, directions but we're getting diaryentry, diaryname,diaryWeight.
So (in this scenario):
The values of name, title, ingredients, directions will be nil
The section-row count will be incorrect because it will give count of both Data as well as Data2 objects (and we don't care about Data2 objects in the RecipesTableTableViewController class... right?... well anyways, I assumed)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
//...
}
We see recipe.name is nil, for some indexPaths, ergo blank rows and vice versa in DiaryTableViewController.
Solution:
Firstly, I wouldn't recommend NSUserDefaults for your purposes but anyways...
Basically, don't use a single #"Added Space Objects Array" key for your NSUserDefaults stuff.
I'd suggest you use 2 separate keys.
//replace
//#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
//with
#define ADDED_SPACE_OBJECTS2 #"RecipeEntries" //in RecipesTableTableViewController
//and
#define ADDED_SPACE_OBJECTS2 #"DiaryEntries" //in DiaryTableViewController
Basically, segregate the entries instead of mixing them up under a single key name.
This seems like the quickest way to solve your problem without changing your logic.

Add new items to UITableView without replacing old items

I intended to add items to a UITableView when this method was called. New items are successfully added (but there's a problem, mentioned below), but I think its odd because I thought "begin updates" to "end updates" was supposed to handle this (Inserting a new row). The initial if condition I had put in never ran so the whole area never got executed. I only realized this recently and updated the condition to what it is now. Now the if block get executed and it crashes the app.
When it is commented out like it is now... New items are added but the newNameOfItem replaces any existing cell labels.
I would like this to add x(newNumberOfItems) new items preferably into a new section each time its called. How can I achieve this?
- (void)addNew:(NSString *)newNumberOfItems :(NSString *)newNameOfItem
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.numberOfItems intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newNumberOfItems intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSLog(#"run = %d", self.run);
Begin updates if statement ...
/*if(self.secondRun){
NSLog(#"run = %d it it", self.run);
[self.tableView beginUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows-[newnumberOfItems intValue] inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
}
*/
[self.tableView reloadData];
}
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell" forIndexPath:indexPath];
cell.textLabel.text = self.nameOfItem;
return cell;
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.numberOfRows;
}
If you want to add new items to a new section every time you call -addNew…, you should create an NSMutableArray of sections where each member is an array of objects representing row data (in this case seems like you'd want NSStrings that represent the item's name. The structure would look something like:
mySections = #[ #[#"section 0 row 0 name", #"section 0 row 1 name", …], #[#"section 1 row 0 name", #"section 1 row 1 name", …], …]
Then in numberOfRowsInSection: return mySection[indexPath.section].count.
Each label has the same value because you're setting every single label for every cell that you dequeue to self.nameOfItem. It's doing exactly what you told it to do. If your intent is to set a different text for every section/row, you have to fetch that text from somewhere. If you created a mySections array as above, you could:
cell.textLabel.text = mySections[indexPath.section][indexPath.row] ;
A note about -addNew:…: simply adding new items to your mySections array will not cause the tableview to update. As you know above, [self.tableView reloadData] will do this for you. However, it will reload the entire table instead of just updating the rows that you added. To do this more efficiently (and with a nice animation), you instead use [self.tableView beginUpdates/endUpdates]. In the case above, where you're adding entire sections and not just rows, you should use insertSections:withRowAnimation:.

Better way to count uncompleted reminders & update table view cell?

I'm using this to count all the uncompleted reminders in the defult list and put it in the detail of the table view cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
NSPredicate *predicate2 = [self.reminderStore predicateForIncompleteRemindersWithDueDateStarting:nil ending:nil calendars:#[[self.reminderStore defaultCalendarForNewReminders]]];
[self.reminderStore fetchRemindersMatchingPredicate:predicate2 completion:^(NSArray *reminders)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d", [reminders count]];
NSLog(#"count: %d", [reminders count]);
}];
...
return cell;
}
The problem is that because it's on another thread, it's not updating the table cell until it needs to reload it. (it does prints in the log). What's the best way of fixing that, so it would load automatically and won't need to load again every time? Also, is there another way to faster count reminders?

Search as-you-type from UISearchBar in a UITableView with multiple sections?

So I'm currently listening to a text change from the searchBar:
-(void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)searchText
{
[self filterContentForSearchText:searchText];
}
I want to design a method filterContentForsearchText such that it automatically filters through my UITableView as I type.
The problem I'm having is that my UITableView is complicated. It has multiple sections with multiple fields. It's basically an contact/address book that is an array of arrays that holds contact objects.
CustomContact *con = [contacts[section_of_contact] allValues][0][x] where x is a specific row in that section returns a CustomContact "con" that has properties like con.fullName.
The UITableView currently displays fullNames of contacts in separate sections. How can I filter through this structure of an array/UITableView as I type using my UISearchBar?
Here's how the table is filled:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BrowseContactCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BROWSECELL"];
CustomContact *thisContact = [self.contacts[indexPath.section] allValues][0][indexPath.row];
cell.labelName.text = thisContact.fullName;
return cell;
}
I also created an address-book with sections (an array of arrays), so I'll just post my solution, which is however not my own solution (I somewhere found it here on stackoverflow some time ago):
In your UITableViewController subclass just add the following two UISearchDisplayDelegate methods (link to the api reference) and the "custom" method filterContentForSearchText to filter through your array. For better understanding, please read my comments in the code blocks. Further questions or ideas for improvement are always welcome.
#pragma mark - search Display Controller Delegate
- (BOOL) searchDisplayController : (UISearchDisplayController *) controller
shouldReloadTableForSearchString : (NSString *) searchString {
[self filterContentForSearchText : searchString
scope : [[self.searchDisplayController.searchBar scopeButtonTitles]
objectAtIndex : [self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
return YES;
}
#pragma mark - Search Filter
- (void) filterContentForSearchText : (NSString*) searchText
scope : (NSString*) scope {
// Here, instead of "lastName" or "firstName" just type your "fullName" a.s.o.
// I have also a custom NSObject subclass like your CustomContact that holds
//these strings like con.firstName or con.lastName
NSPredicate* resultPredicate = [NSPredicate predicateWithFormat : #" (lastName beginswith %#) OR (firstName beginsWith %#)", searchText, searchText];
// For this method, you just don't need to take your partitioned and
// somehow complicated contacts array (the array of arrays).
// Instead, just take an array that holds all your CustomContacts (unsorted):
NSArray* contactsArray = // here, get your unsorted contacts array from your data base or somewhere else you have stored your contacts;
// _searchResults is a private NSArray property, declared in this .m-file
// you will need this later (see below) in two of your UITableViewDataSource-methods:
_searchResults = [contactsArray filteredArrayUsingPredicate : resultPredicate];
// this is just for testing, if your search-Filter is working properly.
// Just remove it if you don't need it anymore
if (_searchResults.count == 0) {
NSLog(#" -> No Results (If that is wrong: try using simulator keyboard for testing!)");
} else {
NSLog(#" -> Number of search Results: %d", searchResults.count);
}
}
Now, you need to do some changes to your following three UITableViewDataSource-methods:
Note: the searchResultsTableView is also loaded with the normal common data source
methods that are usually called to fill your (sectioned address-book-)tableView:
1.
- (NSInteger) numberOfSectionsInTableView : (UITableView *) tableView
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
// in our searchTableView we don't need to show the sections with headers
return 1;
}
else {
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] count];
}
}
2.
- (NSInteger) tableView : (UITableView *) tableView
numberOfRowsInSection : (NSInteger) section {
if (tableView == self.searchDisplayController.searchResultsTableView) {
// return number of search results
return [_searchResults count];
} else {
// return count of the array that belongs to that section
// here, put (or just let it there like before) your
// [contacts[section] allValues][count]
return [[currentMNFaces objectAtIndex : section] count];
}
}
3.
- (UITableViewCell*) tableView : (UITableView *) tableView
cellForRowAtIndexPath : (NSIndexPath *) indexPath {
BrowseContactCell *cell = [tableView dequeueReusableCellWithIdentifier:#"BROWSECELL"];
CustomContact *thisContact = nil;
// after loading your (custom) UITableViewCell
// you probably might load your object that will be put into the
// labels of that tableCell.
// This is the point, where you need to take care if this is your
// address-book tableView that is loaded, or your searchTable:
// load object...
if (tableView == self.searchDisplayController.searchResultsTableView) {
// from search list
faceAtIndex = [self->searchResults objectAtIndex: indexPath.row];
} else {
// from your contacts list
contactAtIndex = [self.contacts[indexPath.section] allValues][0][indexPath.row];
}
}
Hope that works for you.

How to get the values of selected cells of a UITableView in one string

i'm pretty sure this is really simple. But i can't get to make this work. I have a UITableView where i display dynamically a list of facebook friends, thanks to their FBID. Basically, i would like to return the FBIDs of the friends i selected, in one string separated with commas. If possible, in a IBAction, so i can pass them into parameters of a php.
For example, let's pretend i have a list of 4 friends, A B C D, and their ids are 1,2,3,4.
If i select A and C, i would like to have a string which says 1,3.
All i managed to do is to display the id of the friend i select, one at a time.
Here's my code :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
rowcount = [[tableView indexPathsForSelectedRows] count];
indexer = [idFriends objectAtIndex:indexPath.row];
aapell = [NSString stringWithFormat:#"%#", indexer];
NSMutableString * arrayIds = [[NSMutableString alloc] init];
[arrayIds appendString:aapell];
NSLog(#"ids: %#", arrayIds);
}
Thank you in advance, i'm sure this is not complicated.
-(IBAction)gatherFBIds
{
NSString *listOfFacebookIDs = #"";
NSArray *indexPathArray = [self.tableView indexPathsForSelectedRows];
for(NSIndexPath *index in indexPathArray)
{
//Assuming that 'idFriends' is an array you've made containing the id's (and in the same order as your tableview)
//Otherwise embed the ID as a property of a custom tableViewCell and access directly from the cell
//Also assuming you only have one section inside your tableview
NSString *fbID = [idFriends objectAtIndex:index.row];
if([indexPathArray lastObject]!=index)
{
listOfFacebookIDs = [listOfFacebookIDs stringByAppendingString:[fbID stringByAppendingString:#", "]];
}
else
{
listOfFacebookIDs = [listOfFacebookIDs stringByAppendingString:fbID];
}
}
NSLog(#"Your comma separated string is %#",listOfFacebookIDs);
//Now pass this list to wherever you'd like
}
The problem is that you are not using the information in indexPathsForSelectedRows. That is telling you all the selected rows. Instead, you are just looking at indexPath.row, which is the one row most recently selected.
What you need to do is cycle through indexPathsForSelectedRows and gather up the info for every row that is currently selected (I'm assuming you've enabled multiple selection here).
Use the following code to get the selected rows in a table view. :)
NSArray *selectedRows=[tableView indexPathsForSelectedRows];
NSMutableArray *rownumberArray=[[NSMutableArray alloc]init];
for (int i=0; i<selectedRows.count; i++) {
NSIndexPath *indexPath = [selectedRows objectAtIndex:i];
NSInteger row = indexPath.row;
NSNumber *number = [NSNumber numberWithInteger:row];
[rownumberArray addObject:number];
}
// rownumberArray contains the row numbers of selected cells.

Resources