Xcode tableview search filter for duplicate entries - iphone

I have a tableview with some of the same words under different sectionheaders and a searchfield.
When you are searching you will get duplicate results, I would like to filter these duplicate words from my search results.
Any ideas how to do this?

Here is a snippet of code I use to do the exact thing. You need to compare a unique string from each object to identify the duplicate. I do it with "myObject.uniqueID" in this example. Create an array with no duplicates, then [tableView reloadData];
- (void) removeDuplicates{
NSMutableDictionary * d = [[[NSMutableDictionary alloc] initWithCapacity:0] autorelease];
NSMutableArray * noDuplicatesArray = [[[NSMutableArray alloc] initWithCapacity:0] autorelease];
Object* myObject; // whatever your object is
NSString * key;
NSString * value;
for ( int i=0; i<[duplicatesArray count]; i++ ){
myObject = [duplicatesArray objectAtIndex:i];
key = myObject.uniqueID; // whatever makes this object unique (like a stock number, or ID)
value = [d objectForKey:key];
if ( value == nil ){
[d setObject:key forKey:key];
[noDuplicatesArray addObject:deal];
}
}
// now your noDuplicatesArray will have only unique entries.
// reload your tableview using the noDuplicatesArray
}

Related

Performance issue creating Section Index Titles for UITableView

I'm displaying an array of contacts ( [[ContactStore sharedStore]allContacts] ) in a tableview and have divided the list into alphabetic sections. I have used the following code to return an array of the first letters of the contacts, and a dictionary of the number of entries per letter.
//create an array of the first letters of the names in the sharedStore
nameIndex = [[NSMutableArray alloc] init];
//create a dictionary to save the number of names for each first letter
nameIndexCount = [[NSMutableDictionary alloc]init];
for (int i=0; i<[[[ContactStore sharedStore]allContacts]count]; i++){
//Get the first letter and the name of each person
Contact *p = [[[ContactStore sharedStore]allContacts]objectAtIndex:i];
NSString *lastName = [p lastName];
NSString *alphabet = [lastName substringToIndex:1];
//If that letter is absent from the dictionary then add it and set its value as 1
if ([nameIndexCount objectForKey:alphabet] == nil) {
[nameIndex addObject:alphabet];
[nameIndexCount setValue:#"1" forKey:alphabet];
//If its already present add one to its value
} else {
NSString *newValue = [NSString stringWithFormat:#"%d", ([[nameIndexCount valueForKey:alphabet] intValue] + 1)];
[nameIndexCount setValue:newValue forKey:alphabet];
}
}
This works, however it is very slow when the array is large, I'm sure there's a better way to do this but I'm quite new to this so am not sure how. Are there any suggestions for a better way to do this?
Although Bio Cho has a good point, you might see an increase in performance by calling
[[ContactStore sharedStore]allContacts]
only once. For example:
nameIndex = [[NSMutableArray alloc] init];
nameIndexCount = [[NSMutableDictionary alloc] init];
/*
Create our own copy of the contacts only once and reuse it
*/
NSArray* allContacts = [[ContactStore sharedStore] allContacts];
for (int i=0; i<[allContacts count]; i++){
//Get the first letter and the name of each person
Contact *p = allContacts[i];
NSString *lastName = [p lastName];
NSString *alphabet = [lastName substringToIndex:1];
//If that letter is absent from the dictionary then add it and set its value as 1
if ([nameIndexCount objectForKey:alphabet] == nil) {
[nameIndex addObject:alphabet];
[nameIndexCount setValue:#"1" forKey:alphabet];
//If its already present add one to its value
} else {
NSString *newValue = [NSString stringWithFormat:#"%d", ([[nameIndexCount
valueForKey:alphabet] intValue] + 1)];
[nameIndexCount setValue:newValue forKey:alphabet];
}
}
Though I can't say for sure, I'd guess that repeatedly accessing your shared store is what's killing you. Maybe only accessing it once will give you what you need.
Consider storing your contacts in Core Data and using an NSFetchedResultsController.
The NSFetchedResultsController will only load a subset of the rows which are visible on the table view, thus preventing your user from having to wait for all the contacts to be sorted.
NSFetchedResultsController will also sort your contacts by an attribute (ie. first or last name), and you can set your section titles to be the first letter of the field you're sorting by.
Take a look at this question and this tutorial.

Filtering an NSArray from JSON?

I'm trying to implement a searchable tableview in my app, where when someone can search a location and get results. It looks something like this:
I'm getting my source from genomes.com which gives more then just cities, it also has parks, buildings, counties, etc. I want to just show locations which are cities.
The data is a JSON file which is parsed by JSONKit. The whole file comes in (maximum 20 objects) and then the searchable table view shows it. I'm not sure if I should parse the JSON file differently, or if I should make the table view show only the results needed. (Performance in this case is not an issue.). The JSON file gets converted to an NSArray.
Here is part of the array:
{
adminCode1 = MA;
adminCode2 = 027;
adminName1 = Massachusetts;
adminName2 = "Worcester County";
adminName3 = "";
adminName4 = "";
adminName5 = "";
continentCode = NA;
countryCode = US;
countryName = "United States";
elevation = 178;
fcl = A;
fclName = "country, state, region,...";
fcode = ADMD;
fcodeName = "administrative division";
geonameId = 4929431;
lat = "42.2000939";
lng = "-71.8495163";
name = "Town of Auburn";
population = 0;
score = "53.40083694458008";
timezone = {
dstOffset = "-4";
gmtOffset = "-5";
timeZoneId = "America/New_York";
};
toponymName = "Town of Auburn";
},
What I want to do is if the "fcl" (seen in the array) is equal to P, then I want it to show that in the table view. If the "fcl" is some other character, then I don't want it to be seen in the table view. I'm pretty sure that an if statement can do that, but I don't know how to get it so that it filters part of it.
Any help would be appreciated! Thanks
EDIT: As of now, this is the code to search:
- (void)delayedSearch:(NSString*)searchString
{
[self.geoNamesSearch cancel];
[self.geoNamesSearch search:searchString
maxRows:20
startRow:0
language:nil];
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
self.searchDisplayController.searchBar.prompt = NSLocalizedStringFromTable(#"ILGEONAMES_SEARCHING", #"ILGeoNames", #"");
[self.searchResults removeAllObjects];
// Delay the search 1 second to minimize outstanding requests
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(delayedSearch:) withObject:searchString afterDelay:0];
return YES;
}
Your question is basically, how do you filter your array from a search bar string? If so, you can detect when the text changes via UISearchBarDelegate and then go through your array copying those objects that contain the string you are looking for, i.e.
This is the delegate method you want: searchBar:textDidChange:.
[filterArray removeAllObjects];
for(int i = 0; i < [normalArray count]; i++){
NSRange textRange;
textRange =[[[[normalArray objectAtIndex:i] objectForKey:#"name"] lowercaseString] rangeOfString:[searchBarString lowercaseString]];
//I wasn't sure which objectForKey: string you were looking for, just replace the one you want to filter.
if(textRange.location != NSNotFound)
{
[filterArray addObject:[normalArray objectAtIndex:i]];
}
}
filterTableView = YES;
[tableView reloadData];
Note the filterTableView bool value, this is so your tableView knows either to load normally or the filtered version you just made. You implement this in:
tableView:numberOfRowsInSection: //For number of rows.
tableView:cellForRowAtIndexPath: //For the content of the cells.
Hope this is what you were looking for.
NSMutableArray* filtered = [[NSMutableArray alloc] autorelease];
for (int i=0;i<[data count];i++)
{
NSDictionary* item=[data objectAtIndex:i];
if (#"P" == [item objectForKey:#"fcl"] )
{
[filtered addObject:item];
}
}
So every time the search field changes, you will compute a new array, and then reload your tableview. The number of rows will be the numbers of rows in your filtered array.
To compute the new array, you can do this (assuming an array of dictionaries):
NSString *searchString; // from the search field
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[origArray count]];
for(NSDictionary *dict in origArray) {
NSString *val = [dict objectForKey:#"fcl"];
if([val length] >= searchString) {
NSString subString = [val substringToIndex:[searchString length]];
if([subString isEqualToString:val]) [array addObject:dict];
}
}
Each cell then will get its values from the new array.
Just put your json in a NSDictionary and simply do something like :
if ([[yourJson objectForKey:#"fcl"] stringValue] == #"A")
//doSomething

Objective-C iPhone - Ordering data within multiple sections of a UITableView dataSource

For the purpose of asking this question about ordering. The following MyObject class returns an instance with random generated category names.
I use the following dataSource methods:
numberOfSections accessed with [dataSource count].
titleForSection accessed with [[dataSource objectAtIndex:indexPath.section] valueForKey:#"categoryName"].
numberOfRowsInSection accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] count].
And finally, the MyObject for each row is accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] objectAtIndex:indexPath.row] on the cellForRowAtIndexPath method.
I use the following code to create a dataSource that displays 9 section categories, however I'm a little stuck on the ordering of these categories and the data within. Assume there's an NSDate property as part of the MyObject class.
Question: How would I go about using this to display the records in descending order?
- (void)createDatasource
{
NSInteger numberOfObjects = 10;
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:numberOfObjects];
NSMutableArray *categories = [NSMutableArray arrayWithCapacity:numberOfObjects];
for (int i = 0; i < numberOfObjects; i++)
{
MyObject *obj = [[MyObject alloc] init];
[objects addObject:obj];
[categories addObject:obj.category];
[obj release];
}
NSSet *set = [NSSet setWithArray:categories];
NSMutableArray *dataSource = [[NSMutableArray alloc] initWithCapacity:[set count]];
for (NSString *categoryString in set)
{
NSMutableDictionary *mainItem = [[NSMutableDictionary alloc] initWithObjectsAndKeys:nil, #"categoryName", nil, #"myObjects", nil];
NSMutableArray *mainItemMyObjects = [NSMutableArray array];
[mainItem setValue:categoryString forKey:#"categoryName"];
for (MyObject *obj in objects)
{
if ([obj.category isEqualToString:categoryString])
{
[mainItemMyObjects addObject:obj];
}
}
[mainItem setValue:mainItemMyObjects forKey:#"myObjects"];
[dataSource addObject:mainItem];
[mainItem release];
}
NSLog (#"objects = %#\ncategories = %#\nset = %#\ndatasource = %#", objects, categories, set, dataSource);
}
Easiest would be to sort your arrays, using NSMutableArray's sorting mutators or NSArray's sorting methods. Otherwise you'd have to construct some sort of mapping from input indices to dataSource indices for use by the various data source methods.
Edit Requested sample code for sorting, something like this should work. I assume you are wanting to sort everything by a property named date on the MyObject.
// First, sort the myObject mutable array in each category
for (NSDictionary *d in dataSource) {
[[d valueForKey:#"myObjects"] sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Compare dates. NSDate's 'compare:' would do ascending order, so if we just
// reverse the order of comparison they'll come out descending.
return [[o2 date] compare:[o1 date]];
}];
}
// Second, sort the categories by the earliest dated object they contain
[dataSource sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Extract the first object from each category's array, which must be the
// earliest it contains due to the previous sort.
MyObject *myObject1 = [[o1 valueForKey:#"myObjects"] objectAtIndex:0];
MyObject *myObject2 = [[o2 valueForKey:#"myObjects"] objectAtIndex:0];
// Compare dates, as above.
return [[myObject2 date] compare:[myObject1 date]];
}];

NSArray to NSMutableArray as random stack

Just a conceptual description first:
I am reading input from a text file (a list of words) and putting these words into an NSArray using componentsSeparatedByString method. This works.
But I wanted to select the words randomly and then delete them from the array so as to ensure a different word each time. Of course, you cannot change the NSArray contents. So...
I copied the contents of the NSArray into an NSMutableArray and use IT for the selection source. This also works - 269 objects in each array.
To return a word from the NSMutableArray I use the following code:
note- the arrays are declared globally
as
arrMutTextWords = [[NSMutableArray alloc] init]; //stack for words
arrTextWords = [[NSArray alloc] init]; //permanent store for words
-(NSString*) getaTextWord
{
// if the mutable text word array is empty refill
if ([arrMutTextWords count] == 0){
for (int i = 0 ; i < [arrTextWords count]; i++)
[arrMutTextWords addObject:[arrTextWords objectAtIndex:i]];
}
int i = random() % [arrMutTextWords count];
NSString* ptrWord = [arrMutTextWords objectAtIndex:i];
[arrMutTextWords removeObjectAtIndex:i];
return ptrWord;
}
The program crashes during a call to the method above - here is the calling code:
arrTmp is declared globally arrTmp = [[NSArray alloc] init]; //tmp store for words
for (int i = 0 ; i < 4; i++) {
tmpWord = [self getaTextWord];
[arrTmp addObject:tmpWord];
[arrTmp addObject:tmpWord];
}
I'm thinking that somehow deleting strings from arrMutTextWords is invalidating the NSArray - but I can't think how this would occur.
One possible source for problems is your fetching AND removing the NSString object from your list. Removing it releases that NSString instance therefore devalidating your reference.
To be shure to retain a reference you should use this code sequence instead:
NSString * ptrWord = [[[arrMutTextWords objectAtIndex:i] retain] autorelease];
[arrMutTextWords removeObjectAtIndex:i];
return ptrWord;
By the way: You should use
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray: array];
instead of copying all values by hand. While i do not know the implementation of NSMutableArray, i know from times long ago (NeXTstep), that there are several possible optimizations that may speed up basic NSArray operations.
And finally copying this way is much more concise.
Just ran this through XCode and got random words returned, however I skipped the whole for loop and used addObjectsFromArrayfrom NSMutableArray.
NSArray *randomArray = [[NSArray alloc] initWithObjects:#"Paul", #"George", #"John", nil];
NSMutableArray *muteArray = [[NSMutableArray alloc] init];
[muteArray addObjectsFromArray:randomArray];
int i = random() % [muteArray count];
NSString* ptrWord = [muteArray objectAtIndex:i];
[muteArray removeObjectAtIndex:i];
NSLog(#"ptrWord %#", ptrWord); //gave me a different name each time I ran the function.
Hope this clears some things up.

Filter a NSMutableArray index beyond bounds Problem

I am trying to filter a NSMutableArray. Search array for items by certain country. I have tried NSpredicate which works great however I need to re-use the original array which I cannot with NSpredicate. So I am trying the below code.
Q. What is the best way to filter an NSMutableArray keeping the original array intact?
//The following code works
filteredArray = [[NSMutableArray alloc] init];
unsigned int i;
for (i = 0; i < [appDelegate.arrayToBeFiltered count]; i++) {
id session = [appDelegate.ads objectAtIndex: i];
id Country = [[appDelegate.arrayToBeFiltered objectAtIndex: i] TheCountry];
[filteredArray addObject: session];
However when I add the if statement as below I get index beyond bounds
filteredArray = [[NSMutableArray alloc] init];
unsigned int i;
for (i = 0; i < [appDelegate.arrayToBeFiltered count]; i++) {
id session = [appDelegate.ads objectAtIndex: i];
id Country = [[appDelegate.arrayToBeFiltered objectAtIndex: i] TheCountry];
if (Country == #"United States"){
[filteredArray addObject: session];
}
}
Use - (NSArray *)filteredArrayUsingPredicate:(NSPredicate *)predicate
As a side-note, in the code above you probably mean to do this [Country isEqualToString: #"United States"]
As an extra side note - don't capitalise variables and method names. Just a style thing but capitalisation is usually reserved for Class names