uisearchbar uisearchdisplaycontroller tableview subtitle error - objective-c

I have a little problem with my searchbar for a tableview
I have made a tableview with an array tableViewArray.
The tableViewArray consists of many rows of another array consisting of [text, distance].
Everything is working fine.
Now i added a searchBar and a searchdisplaycontroller, that searched based on a new array of string (from the "text" object of the tableViewArray).
I thought the search should only be available for the text, and the search method is implemented on that.
Now when i get the search result, it looks good, and the search returns expected rows. The problem is with the search tableViews subtitle. It is showing the distances for row 1, 2, 3 for the tableViewArray.
I need it to map the distance to the text shown in the search tableview rows.
I imagine i need to make a new table view array for the search results consisting of [text distance]. the text is not a problem since it is from the search result, but how do i map the new distance to the old distance???
The search method im using in the search delegate is :
searchResults = [[NSArray alloc]init];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#",
searchText];
searchResults = [searchItems filteredArrayUsingPredicate:resultPredicate];
Hope somebody can help :) Thanks in advance!
The original code:
- (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section
{
if (theTableView == self.searchDisplayController.searchResultsTableView) {
return [searchResults count];
} else {
return [tableViewArray count];
}
}
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleSubtitle
reuseIdentifier: CellIdentifier] ;
if (theTableView == self.searchDisplayController.searchResultsTableView) {
} else {
cell = [theTableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
}
// Configure the cell...
// cell.textLabel.numberOfLines = 0;
// cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
if (theTableView == self.searchDisplayController.searchResultsTableView) {
cell.textLabel.text = [searchResults objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = [[[tableViewArray objectAtIndex:indexPath.row] objectAtIndex:0] subtitle];
}
cell.detailTextLabel.textColor = [UIColor redColor];
float blabla= [[[tableViewArray objectAtIndex:indexPath.row] objectAtIndex: 1] doubleValue];
if (blabla < 1000) {
cell.detailTextLabel.text = [NSString stringWithFormat:#"%.2f m",blabla];
} else {
cell.detailTextLabel.text = [NSString stringWithFormat:#"%.2f km",blabla/1000];
}
NSString *text = [[[tableViewArray objectAtIndex:indexPath.row] objectAtIndex:0] subtitle];
NSRange q8RangeValue = [text rangeOfString:#"Q8" options:NSCaseInsensitiveSearch];
NSRange okRangeValue = [text rangeOfString:#"OK" options:NSCaseInsensitiveSearch];
if (q8RangeValue.length >0 ) {
cell.imageView.image = [UIImage imageNamed:#"q8.png"];
} else if (okRangeValue.length >0 ) {
cell.imageView.image = [UIImage imageNamed:#"OK logo.png"];
} else {
cell.imageView.image = nil;
}
return cell;
}
And where i make the array for the search:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"didUpdateToLocation Location calculator distance array for the tableView");
NSMutableArray * distancesInReverseOrder = [[NSMutableArray alloc] init];
for (int i = 0; i<allAnnotations.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0] ;
CLLocationCoordinate2D annotationCoord = [[allAnnotations objectAtIndex:indexPath.row] coordinate];
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotationCoord.latitude longitude:annotationCoord.longitude];
distanceToMe = [newLocation distanceFromLocation:location];
[distancesInReverseOrder insertObject:[NSNumber numberWithFloat: distanceToMe] atIndex:0];
}
distances = [[NSMutableArray alloc] initWithArray:[[distancesInReverseOrder reverseObjectEnumerator] allObjects]];
// Assuming you have your points on the map in an NSArray called
// allAnnotations and your distances in distances, create a
// new mutable array to hold both
tableViewArray = [[NSMutableArray alloc] init];
// Iterate over all of the points, and add a new element to the mutable
// array which is a new array containing a point and its distance
for (int i = 0; i < allAnnotations.count; i++) {
NSArray *newItem = [NSArray arrayWithObjects: [allAnnotations objectAtIndex: i], [distances objectAtIndex: i], nil];
[tableViewArray addObject: newItem];
}
// Now, sort the new array based upon the distance in the second element
// of each array (ie, the distance).
[tableViewArray sortUsingComparator: ^(id obj1, id obj2) {
NSNumber *dist1 = [obj1 objectAtIndex:1];
NSNumber *dist2 = [obj2 objectAtIndex:1];
return [dist1 compare:dist2];
}];
searchResults = [NSMutableArray arrayWithCapacity:[tableViewArray count]];
searchItems = [[NSMutableArray alloc] init];
for (int i = 0; i < allAnnotations.count; i++) {
NSArray *newItem = [NSArray arrayWithObjects: [[[tableViewArray objectAtIndex:i] objectAtIndex:0] subtitle], #"bla", nil];
[searchItems addObject: newItem];
}
/*
for (int i=0; i<tableViewArray.count; i++) {
[searchItems insertObject:[[[tableViewArray objectAtIndex:i] objectAtIndex:0] subtitle] atIndex:0];
}
*/
NSLog(#"searchitems count is %i", searchItems.count);
[tableView reloadData];
[locationManager stopUpdatingLocation];
}
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
/*
// Update the filtered array based on the search text and scope.
// Remove all objects from the filtered search array
[searchResults removeAllObjects];
// Filter the array using NSPredicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",searchText];
// searchResults = [NSMutableArray arrayWithArray:[[tableViewArray objectAtIndex:1] filteredArrayUsingPredicate:predicate]];
*/
/*
searchResults = [[NSArray alloc]init];
NSPredicate *resultPredicate = [NSPredicate
predicateWithFormat:#"SELF contains[cd] %#",
searchText];
searchResults =[searchItems filteredArrayUsingPredicate:resultPredicate];
*/
// Create index set of all objects in textArray that contain searchText:
NSIndexSet *set = [searchItems indexesOfObjectsPassingTest:
^BOOL(NSString *text, NSUInteger idx, BOOL *stop) {
NSRange range = [text rangeOfString:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
return (range.location != NSNotFound);
}];
// Filter textArray:
filteredTextArray = [searchItems objectsAtIndexes:set];
// Filter distanceArray:
filteredDistanceArray = [distances objectsAtIndexes:set];
NSLog(#"filtered text array is %#", filteredTextArray);
NSLog(#"filtered distance array is %#",filteredDistanceArray);

If I understand your problem correctly, you have 2 separate arrays that are used as data source for the table view, let's call them textArray and distanceArray.
Now you filter the textArray according to the search string and you need the "corresponding" filtering of distanceArray.
One way to do this is to replace filteredArrayUsingPredicate with indexesOfObjectsPassingTest, because that returns a set of matching indices that can be applied to both arrays:
// Create index set of all objects in textArray that contain searchText:
NSIndexSet *set = [textArray indexesOfObjectsPassingTest:
^BOOL(NSString *text, NSUInteger idx, BOOL *stop) {
NSRange range = [text rangeOfString:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
return (range.location != NSNotFound);
}];
// Filter textArray:
filteredTextArray = [textArray objectsAtIndexes:set];
// Filter distanceArray:
filteredDistanceArray = [distanceArray objectsAtIndexes:set];
Now you can use filteredTextArray and filteredDistanceArray as data source for the search table view.
Alternatively, you could use a single array as data source if each object in the array is for example a dictionary containing both text and distance for one row.
UPDATE: As I understand it now, each item of your tableViewArray is an array with 2 items (one for the text and one for the distance).
In this case I would recommend to filter the tableViewArray directly:
NSIndexSet *set = [tableViewArray indexesOfObjectsPassingTest:
^BOOL(NSArray *item, NSUInteger idx, BOOL *stop) {
NSString *subtitle = [[item objectAtIndex:0] subtitle];
NSRange range = [subtitle rangeOfString:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)];
return (range.location != NSNotFound);
}];
searchResults = [tableViewArray objectsAtIndexes:set];
Now searchResults is the filtered array, and each item has the same structure as the items in tableViewArray.
This simplifies things in cellForRowAtIndexPath, e.g.
if (theTableView == self.searchDisplayController.searchResultsTableView) {
cell.textLabel.text = [[[searchResults objectAtIndex:indexPath.row] objectAtIndex:0] subtitle];
distance = [[[searchResults objectAtIndex:indexPath.row] objectAtIndex: 1] doubleValue];
} else {
cell.textLabel.text = [[[tableViewArray objectAtIndex:indexPath.row] objectAtIndex:0] subtitle];
distance = [[[tableViewArray objectAtIndex:indexPath.row] objectAtIndex: 1] doubleValue];
}

Related

Memory leak while handling Foundation Object from NSJSONSerialization

I'm struggling to fix a memory leak in a helper function I have made. The helper function takes the result of
+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError * _Nullable *)error
and converts all the leaf elements into NSStrings if they are NSNumbers.
Here is the method:
-(NSArray *) stringisizeObjects:(NSArray *)inputArray{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *mutable = [[NSMutableArray alloc] initWithCapacity:[inputArray count]];
for (int i = 0; i < [inputArray count]; i++) {
NSArray *keys = [inputArray[i] allKeys];
NSMutableDictionary *addDictionary = [[NSMutableDictionary alloc] initWithCapacity:[keys count]];
for (int j = 0; j < [keys count]; j++) {
id theObject = [[inputArray[i] objectForKey:keys[j]]autorelease];
if ([theObject isKindOfClass:[NSNumber class]]) {
[addDictionary setObject:[theObject stringValue] forKey:keys[j]];
[theObject release];
}else if ([theObject isKindOfClass:[NSString class]]){
[addDictionary setObject:[inputArray[i] objectForKey:keys[j]] forKey:keys[j]];
}
}
[mutable addObject:addDictionary];
}
NSArray *returnArray = [mutable copy];
[mutable removeAllObjects];
[mutable release];
[pool drain];
return returnArray;
}
Here is how I get the input array.
id parsedThingy = [NSJSONSerialization JSONObjectWithData:resultJSONData options:1 error:&jsonDecodeError];
Before I can pass the result to my stringisize method I must ensure that I have an NSArray of NSDictionaries with matching keys.
NSArray *resultArray = [self stringisizeObjects:parsedThingy];
The X-Code memory leaks tool has pointed me to this method as the cause of my problem.
Instruments showing leaks
As you can see I have tried wrapping things in autorelease pools, autoreleasing and releasing. I just don't see any way forward here.
This is a non ARC project that runs 24/7.
Edit: I took the advice from Droppy and tried to re-write the method using mutableCopy. The leak is still there. At this point my only work around maybe to change the source of the JSON to send only strings. :(
-(NSArray *) stringisizeObjects2:(NSArray *)inputArray{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *mutableArray = [inputArray mutableCopy];
for (int i = 0; i < [mutableArray count]; i++) {
NSMutableDictionary *mutableDict = [mutableArray[i] mutableCopy];
NSArray *keys = [mutableDict allKeys];
for (int j = 0; j < [keys count]; j++) {
if ([[mutableDict objectForKey:keys[j]] isKindOfClass:[NSNumber class]]) {
NSString *stringValue = [[mutableDict objectForKey:keys[j]] stringValue];
[mutableDict removeObjectForKey:keys[j]];
[mutableDict setObject:stringValue forKey:keys[j]];
}
}
mutableArray[i] = [mutableDict copy];
[mutableDict release];
}
NSArray *returnArray = [mutableArray copy];
[mutableArray release];
[pool drain];
return returnArray;
}
problem:
addDictionary called alloc but not call release or autorelease
returnArray = [mutable copy]; // did increase retainCount +1, need autorelease here
id theObject = [inputArray[i] objectForKey:keys[j]]; // not need autorelease or release for object that You not own
add NSAutoreleasePool to top an bottom here just do nothing
solution:
-(NSArray *) stringisizeObjects:(NSArray *)inputArray{
NSMutableArray *mutable = [[NSMutableArray alloc] initWithCapacity:[inputArray count]];
for (int i = 0; i < [inputArray count]; i++) {
NSArray *keys = [inputArray[i] allKeys];
NSMutableDictionary *addDictionary = [[NSMutableDictionary alloc] initWithCapacity:[keys count]];
for (int j = 0; j < [keys count]; j++) {
id theObject = [inputArray[i] objectForKey:keys[j]]; // not need autorelease
if ([theObject isKindOfClass:[NSNumber class]]) {
[addDictionary setObject:[theObject stringValue] forKey:keys[j]];
//[theObject release]; // not need release value here
}else if ([theObject isKindOfClass:[NSString class]]){
[addDictionary setObject:[inputArray[i] objectForKey:keys[j]] forKey:keys[j]];
}
}
[mutable addObject:addDictionary];
[addDictionary release]; // release after not use
}
NSArray *returnArray = [[[NSArray alloc] initWithArray:mutable] autorelease]; // auto release for return value
[mutable removeAllObjects];
[mutable release];
return returnArray;
}

obj c -get list of indexes in NSArray from NSPredicate

I have an array of car and I am filtering it based on objects containing the letter i.
NSMutableArray *cars = [NSMutableArray arrayWithObjects:#"Maruthi",#"Hyundai", #"Ford", #"Benz", #"BMW",#"Toyota",nil];
NSString *stringToSearch = #"i";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#",stringToSearch]; // if you need case sensitive search avoid '[c]' in the predicate
NSArray *results = [cars filteredArrayUsingPredicate:predicate];
results contains Maruthi,Hyundai. Instead of the elements, I want results to contain the indexes of the elements i.e 0,1.
NSMutableArray *cars = [NSMutableArray arrayWithObjects:#"Maruthi",#"BMW", #"Ford", #"Benz", #"Hyundai",#"Toyota",nil];
NSMutableArray * results = [[NSMutableArray alloc]init];
for(int i = 0;i<cars.count;i++)
{
NSString * obj = [cars objectAtIndex:i];
if([obj rangeOfString:#"i"].location == NSNotFound)
{
NSLog(#"Not Found");
}
else
{
int index = [cars indexOfObject:obj];
[results addObject:[NSNumber numberWithInt:index]];
}
}
Why not use
- (NSIndexSet *)indexesOfObjectsPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
Or similar?
Depending on your search criteria, something like this perhaps?
NSArray *array = #[#"Maruthi",#"Hyundai", #"Ford", #"Benz", #"BMW", #"Toyota"];
NSString *stringToSearch = #"i";
NSIndexSet *set = [array indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
NSString *string = obj;
if (([string rangeOfString:stringToSearch].location == NSNotFound))
{
return NO;
}
return YES;
}];

Get the correct index of dictionary

NSMutableArray *tmpMutArr = [NSMutableArray arrayWithArray:allObjectsArray];
NSLog(#"The content of array is%#",tmpMutArr);
int index;
for (int i=0;i<[tmpMutArr count];i++)
{
if([[tmpMutArr objectAtIndex:i] isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *tempDict = [tmpMutArr objectAtIndex:i];
if([[tempDict valueForKey:#"Name"] isEqualToString:[NSString stringWithFormat:#"%#", nameString]])
{
index = i;
}
}
}
[tmpMutArr replaceObjectAtIndex:index withObject:[NSDictionary dictionaryWithDictionary:mutDict]];
This code is not replacing the matching object in tmpMutArr as I want it to, but replaces all objects in tmpMutArr instead. How to replace only the index I want?
I know that tmpMutArr containing all objects before the replacement, so I just need to specify the index correctly I think. How to do so?
NSMutableArray *tmpMutArr = [NSMutableArray arrayWithArray:allObjectsArray];
NSLog(#"The content of array is%#",tmpMutArr);
int index;
for (int i=0;i<[tmpMutArr count];i++)
{
if([[tmpMutArr objectAtIndex:i] isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *tempDict = [tmpMutArr objectAtIndex:i];
if([[tempDict valueForKey:#"Name"] isEqualToString:nameString])
{
index = i;
break; // << added break
}
}
}
[tmpMutArr replaceObjectAtIndex:index withObject:[NSDictionary dictionaryWithDictionary:mutDict]];
maybe, you should try to this version... you have not specified which index is needed, I suppose the first one.
for (int i=0;i<[tmpMutArr count];i++) {
if([[tmpMutArr objectAtIndex:i] isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *tempDict = [tmpMutArr objectAtIndex:i];
if([[tempDict valueForKey:#"Name"] isEqualToString:[NSString stringWithFormat:#"%#", nameString]]) {
index = i;
break; // when you find the first one, you should go out from the iteration
}
}
}

If textField is empty, do not filter my array to nothing

This works fine if the textField is containing any text. But if the textField is empty, no objects are added.
How to tell this piece of code that if the textField is empty, all objects should be added to resultObjectsArray?
resultObjectsArray = [NSMutableArray array];
for(NSDictionary *object in allObjectsArray)
{
NSString *nameString = [NSString stringWithFormat:#"%#", [textField text]];
NSString *wineName = [wine objectForKey:#"Name"];
NSRange range = [wineName rangeOfString:nameString options:NSCaseInsensitiveSearch];
if ((range.location != NSNotFound))
[resultObjectsArray addObject:object];
}
resultObjectsArray = [NSMutableArray array];
NSString *nameString = [textField text];
for(NSDictionary *object in allObjectsArray)
{
if ([nameString length] > 0)
{
NSString *wineName = [wine objectForKey:#"Name"];
NSRange range = [wineName rangeOfString:nameString options:NSCaseInsensitiveSearch];
if ((range.location != NSNotFound))
[resultObjectsArray addObject:object];
}
else
{
[resultObjectsArray addObject:object];
}
}
Note that you don't need:
NSString *nameString = [NSString stringWithFormat:#"%#", [textField text]];
as
NSString *nameString = [textField text];
is sufficient. Also it won't change inside the for loop so can be initialised outside of it.
That's how the code ended up looking like using the answer from #trojanfoe. (I've excluded my other search criterias).
-(IBAction)searchButtonPressed:(id)sender{
BOOL textFieldIsEdited = NO;
if ([[textField text] length] > 0)
{
textFieldIsEdited = YES;
}
resultObjectsArray = [NSMutableArray array];
for(NSDictionary *wine in allObjectsArray)
{
BOOL nameMatch = YES;
if (textFieldIsEdited) {
NSString *wineName = [wine objectForKey:#"Name"];
NSString *nameString = [textField text];
NSRange range = [wineName rangeOfString:nameString options:NSCaseInsensitiveSearch];
if (range.location == NSNotFound) {
nameMatch = NO;
}
}
if ((nameMatch != NO))
[resultObjectsArray addObject:wine];
}

Get matched string from two NSArrays

How can I save the string that match from one NSArray with one index difference in NSMutableArray?
For example, there are three "apple", four "pineapple", six "banana", two "cocoa" and the rest of words dont have duplicate(s) in the nsarray, i would like to know if the nsarray has at least two same words. If yes, I would like to save "apple", "pineapple, "banana" and "cocoa" once in nsmutablearray. If there are other alike words, I would like to add them to namutablearray too.
My code (which still doesn't work properly);
NSArray *noWords = [[NSArray alloc] initWithArray:
[[NSString stringWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"words" ofType:#"txt"]
encoding:NSUTF8StringEncoding error:NULL]
componentsSeparatedByString:#"\n"]];
NSUInteger scount = [noWords count];
int ii = 0;
NSString *stringline;
for (ii; ii < scount; ii++)
{
stringline = [noWords objectAtIndex:ii];
NSLog(#"stringline : %# ", stringline);
}
int i = 1;
NSString *line;
for (i ; i < 10; i++)
{
line = [noWords objectAtIndex:i];
NSLog (#"line : %# ", line);
NSMutableArray *douwords = [NSMutableArray array];
if ([stringline isEqualToString:line])
{
NSString *newword;
for (newword in douwords)
{
[douwords addObject:newword];
NSLog (#"detected! %# ", douwords);
}
}
}
Here's a solution using two sets:
- (NSArray *)getDuplicates:(NSArray *)words
{
NSMutableSet *dups = [NSMutableSet set],
*seen = [NSMutableSet set];
for (NSString *word in words) {
if ([seen containsObject:word]) {
[dups addObject:word];
}
[seen addObject:word];
}
return [dups allObjects];
}
Assuming NSSet uses hash tables behind the scenes (which I'm betting it does), this is going to be faster than the previously suggested O(n^2) solution.
Here's something off the top of my head:
NSMutableSet* duplicates = [NSMutableSet set];
NSArray* words = [NSArray arrayWithObjects:#"Apple", #"Apple", #"Orange", #"Apple", #"Orange", #"Pear", nil];
[words enumerateObjectsUsingBlock:^(NSString* str, NSUInteger idx, BOOL *stop) {
for (int i = idx + 1; i < words.count; i++) {
if ([str isEqualToString:[words objectAtIndex:i]]) {
[duplicates addObject:str];
break;
}
}
}];
NSLog(#"Dups: %#", [duplicates allObjects]); // Prints "Apple" and "Orange"
The use of an NSSet, as opposed to an NSArray, ensures strings are not added more than once. Obviously, there are optimizations that could be done, but it should be a good starting point.
I assume that you want to count appearances of words in your array and output those with a count of more than one. A basic and verbose way to do that would be:
// Make an array of words - some duplicates
NSArray *wordList = [[NSArray alloc] initWithObjects:
#"Apple", #"Banana", #"Pencil",
#"Steve Jobs", #"Kandahar",
#"Apple", #"Banana", #"Apple",
#"Pear", #"Pear", nil];
// Make an mutable dictionary - the key will be a word from the list
// and the value will be a number representing the number of times the
// word appears in the original array. It starts off empty.
NSMutableDictionary *wordCount = [[NSMutableDictionary alloc] init];
// In turn, take each word in the word list...
for (NSString *s in wordList) {
int count = 1;
// If the word is already in the dictionary
if([wordCount objectForKey:s]) {
// Increse the count by one
count = [[wordCount objectForKey:s] intValue] + 1;
}
// Save the word count in the dictionary
[wordCount setObject:[NSNumber numberWithInt:count] forKey:s];
}
// For each word...
for (NSString *s in [wordCount keysOfEntriesPassingTest:
^(id key, id obj, BOOL *stop) {
if ([obj intValue] > 1) return YES; else return NO;
}]) {
// print the word and the final count
NSLog(#"%2d %#", [[wordCount objectForKey:s] intValue], s);
}
The output would be:
3 Apple
2 Pear
2 Banana