Trying to sort sections in descending order - objective-c

self.sections = [[NSMutableDictionary alloc] init];
BOOL found;
for (NSDictionary *wine in sortedWines)
{
NSNumber *rate = [wine valueForKey:#"Rate"];
NSString *rateStr = [NSString stringWithFormat:#"%.f", [rate floatValue]];
found = NO;
for (NSString *str in [self.sections allKeys])
{
if ([str isEqualToString:rateStr])
{
found = YES;
}
}
if (!found)
{[self.sections setValue:[[NSMutableArray alloc] init] forKey:rateStr];}
}
for (NSDictionary *wine in sortedWines)
{[[self.sections objectForKey:[NSString stringWithFormat:#"%.f", [[wine valueForKey:#"Rate"] floatValue]] ] addObject:wine];}
// Sort:
for (NSString *key in [self.sections allKeys])
{[[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"Rate" ascending:NO]]];}
This code puts my wines in sections, but it won't sort them in descending order! Could it be because the NSNumber is transformed into NSString? I've tried to make a code using the NSNumber value:
self.sections = [[NSMutableDictionary alloc] init];
BOOL found;
for (NSDictionary *wine in sortedWines)
{
NSNumber *rate = [wine valueForKey:#"Rate"];
found = NO;
for (NSNumber *str in [self.sections allKeys])
{
if ([str isEqualToNumber:rate])
{
found = YES;
}
}
if (!found)
{[self.sections setValue:[[NSMutableArray alloc] init] forKey:rate];}
}
// Loop again to sort wines into their keys
for (NSDictionary *wine in sortedWines)
{[[self.sections objectForKey:[wine valueForKey:#"Rate"]] addObject:wine];}
// Sort each section array
for (NSString *key in [self.sections allKeys])
{[[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"Rate" ascending:NO]]];}
But it gives a warning for
if (!found)
{[self.sections setValue:[[NSMutableArray alloc] init] forKey:rate];}
that says "Incompatible pointer types sending NSNumber ___strong to parameter of type NSString"
If I run the app it crashes with error -[__NSCFNumber localizedCaseInsensitiveCompare:]: unrecognized selector sent to instance 0x1e085810
What do I have to change to make it work and sort the sections in descending order? Thanks.

I don't know if the default selector for sortDescriptorWithKey:ascending: is now caseInsensitiveCompare:, I'm pretty sure it used to be just compare:. In any case, you can use sortDescriptorWithKey:ascending:selector:, instead, and pass compare: for the selector. I think that should fix your second error. Still not sure why you're getting that first error.

You would do much better (and we'd understand you better) if you formatted your code for legibility. Eg:
// Loop again to sort wines into their keys
for (NSDictionary *wine in sortedWines) {
NSArray* section = [self.sections objectForKey:[wine valueForKey:#"Rate"]];
[section addObject:wine];
}
// Sort each section array
NSArray* sortDescriptorArray = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"Rate" ascending:NO]];
for (NSString *key in [self.sections allKeys]) {
NSArray* section = [self.sections objectForKey:key];
[section sortUsingDescriptors:sortDescriptorArray];
}
Among other things, this makes debugging much simpler since you can stop and dump the section arrays.
Or, if you really liked it better the other way, I can highly recommend that you learn APL or LISP instead.

Related

UISearchBar and NSDictionary

i want to add to my app a UISearchBar.
I have 3 NSDictionary that contains respectively 3 arrays (sorted). The 3 NSDictionaries have the same key (#"Elements"), stored in a NSMutableArray called listObjects.
At the end of the 3 dicts i have this situation:
[listObjects addObject:dictOne];
[listObjects addObject:dictTwo];
[listObjects addObject:dictThree];
I also have a filteredListContent to store the arrays (for quick searching).
filteredListContent = [[NSMutableArray alloc] initWithCapacity: [listObjects count]];
[filteredListContent addObjectsFromArray: listObjects];
Now, the first question is: does the filteredListContent array store all the 3 dict keys from listObjects?
Then.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
[self.filteredListContents removeAllObjects];
NSString *cellTitle;
for (cellTitle in listObjects){
NSComparisonResult result = [cellTitle compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame){
[filteredListContents addObject:cellTitle];
}
}
}
I got a crash as i begin typing in
NSComparisonResult result = [cellTitle compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])];`
line.
Is there supposed to be the key of the dict (#"Elements"), in order to search in all the 3 arrays or?
Edit
Full code
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSDictionary *dictionary = [listObjects objectAtIndex:section];
NSArray *array = [dictionary objectForKey:#"Elements"];
return [array count];
}
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
NSString *cellTitle;
for (NSDictionary *dict in listaObjects)
{
NSArray *elements = [dict objectForKey: #"Elements"];
for (NSString *cellTitle in elements)
if ([cellTitle compare: searchText options: NSCaseInsensitiveSearch range: NSMakeRange(0, [searchText length])] = NSOrderedSame)])
[filteredListContent addObject: cellTitle];
}}
#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption{
[self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:searchOption]];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
I got it working, but it shows the contents of all the arrays, even if i type a specific word. What am i doing wrong?
I don't get it. You first add the three dictionaries to filteredListContents and then you remove all objects from the array? That means the array is totally empty again.
Then you enumerate listObjects, which only contains 3 dictionaries, and no strings, using an NSString enumerator. That doesn't make sense.
First, there is no need to do:
[filteredListContents addObjectsFromArray: listObjects];
So remove that line. Now do something like:
for (NSDictionary *dict in listObjects)
{
NSArray *elements = [dict objectForKey: #"Elements"];
for (NSString *cellTitle in elements)
if (cellTitle compare: searchText options: options: NSCaseInsensitiveSearch range: NSMakeRange(0, [searchText length])] = NSOrderedSame)
[filteredListContents addObject: cellTitle];
}
OK, that gives you an array of equal strings. Now what? I'd expect you to collect the cells with that title, but you are not doing that here. You could, instead of storing strings, store dictionaries with the cell title as key and the cell as value. That would make the loop only one level deeper.
As someone wiser than me once said: Show me your problem, not your solution. Then I can help you.
The full code doesn't make any sense to me, sorry. What are you trying to collect actually?

iOS/Objective-C: Attempting to Scan a string for substrings which will be assigned to multiple NSStrings

I'm attempting to complete the Stanford iPhone Programming (FA10) assignement "Flickr Fetcher" -- so far things are going well, however I have come to an impasse:
I have successfully extracted the location of the "Top 100" pictures, which are formated in a string as "Country, State, City". I would like to create two NSStrings -- one being the country, the other string being the State and City. From where I can then do
cell.textLabel.text = countryString;
cell.detailTextLabel.text = stateCityString;
in my table view datasource methods.
From research on stackoverflow and the Apple Documentaion, NSScanner seems to be my best bet -- here is what I have so far...
- (void)viewDidLoad {
//Get the top 100 photos from Flickr
self.topPlacesArray = [FlickrFetcher topPlaces];
NSString *mainLabelString = [[NSString alloc] init];
NSString *stringFromArray = [[NSString alloc] init];
//This retrieves the string of the location of each photo
stringFromArray = [topPlacesArray valueForKey:#"_content"];
NSScanner *theScanner = [NSScanner scannerWithString:stringFromArray];
NSCharacterSet *commaSet = [[NSCharacterSet alloc] init];
commaSet = [NSCharacterSet characterSetWithCharactersInString:#","];
while ([theScanner isAtEnd] == NO) {
if ([theScanner scanUpToCharactersFromSet:commaSet intoString:&stringFromArray]) {
NSLog(#"%#",stringFromArray);
}
}
I'm just trying to see if the string properly substrings itself -- however I am getting a "SIGBART" at the beggining of the while loop, the error is this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI length]: unrecognized selector sent to instance 0x8939eb0'
From all the documentation I have seen on NSScanner, it seems I have it set up properly, however, no matter what changes I do, it seems unable to even begin the loop.
What do I have to do to set up NSScanner properly, to avoid the "SIGABRT"? (for the record, i'm assuming "SIGABRT" is a segfault?). Thank you all for your time, you all are the best!
(Btw: I know this is not fully implemented yet for both country and state-city, i just want to get used to NSScanner, I will implement the rest once I get NSScanner under control)
EDIT 1: SosBorn! You are incredible! Thank you so much! So I have implemented this for my viewDidLoad:
- (void)viewDidLoad
{
self.topPlacesArray = [FlickrFetcher topPlaces];
NSArray *ArrayOfStrings = [[NSArray alloc] init];
NSArray *placeElements = [[NSArray alloc] init];
NSString *country = [[NSString alloc] init];
NSString *city = [[NSString alloc] init];
NSString *state = [[NSString alloc] init];
ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
for (NSString *place in ArrayOfStrings) {
placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
country = [placeElements objectAtIndex:2];
[self.countryArray addObject:country];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Did this work?");
}
}
[ArrayOfStrings release];
[placeElements release];
[country release];
[city release];
[state release];
[super viewDidLoad];
}
This worked like a complete charm BUT i'm having some bad access going on in the Delegate when trying to access self.window.rootViewController = self.viewController -- this doesn't make any-sense (i actually have a completely empty table, etc...) -- so i'm thinking I played with bad memory management with my substring-ing and now it gets in trouble with this delegate call.
Chuck, I was very interested in your comment as I was taught that the proper way to make variables is to call [myclass alloc] init]; and then release when you are done -- as I have. Of course my objective-C greenness is showing a bit... blush.
You all and this incredible community are such an asset to us Students -- thank you for all your time and dedication. The only path to progress is a path of cooperation!
EDIT 2: Ok -- now it's totally fixed with no terrible leaking problems. Chuck you were right! I had the pricniples of alloc init completely mixed up in my head -- here was my final solution:
NSMutableArray *array1 = [[NSMutableArray alloc] init];
NSMutableArray *array2 = [[NSMutableArray alloc] init];
NSMutableArray *array3 = [[NSMutableArray alloc] init];
self.cityArray = array1;
self.countryArray = array2;
self.stateArray = array3;
[array1 release];
[array2 release];
[array3 release];
NSArray *ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
NSArray *topPlaces = [NSArray arrayWithArray:ArrayOfStrings];
NSArray *topPlacesSorted = [topPlaces sortedArrayUsingSelector:#selector(compare:)];
ArrayOfStrings = topPlacesSorted;
for (NSString *place in ArrayOfStrings) {
NSArray *placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
NSString *city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
NSString *state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
NSString *country = [placeElements objectAtIndex:2];
NSString *stateAndCountry = [NSString stringWithFormat:#"%#, %#", state, country];
[self.countryArray addObject:stateAndCountry];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Nil Request");
}
Thank you again SosBorn, i was feeling like I had forgotten the basics of CS ಠ_ಠ.
The only thing that really bothers me is why do we have to initialize instance NSMutableArrays that way -- i found this was the only way to get them to actually work.
Not totally sure why it is crashing, but I think another approach to this would serve you better. You have a topPlacesArray, why not iterate through the array and process each array entry seperately? I am making some assumptions about the topPlacesArray, but it would look something like this:
for (NSString *place in topPlacesArray)
{
//Place is probably in this format: "Country, State, City"
NSArray *placeElements = [place componentsSeperatedByString:#","];
//This should give you an array with three elements. Country State and city.
NSString *country = [placeElements objectAtIndex:0];
NSString *cityState = [NSString stringWithFormat:#"%#, %#", country, cityState];
//Now you have your strings that you need. Do whatever you need to do with them.
//Add them to an array or set the value of a text label, etc.
}
Didn't take the time to handle memory management but you get the idea.

NSArray with componentsSeparatedByString memory leak

I'm having problems with memory leaks with this function. I thought creating an NSArray with componentsSeparatedByString was autorelease but instruments seems to indicate a leak at the NSArray aPair. Why would it indicate a leak there and not also at the other NSArrays created in the same way?
-(void) checkRequest: (NSString *)request view:(UIViewController *)theView webView:(UIWebView *)wView
{
//NSLog(#"JSResponder - checkRequest()");
NSString *aRequest = [NSString stringWithString:request];
NSArray *urlArray = [aRequest componentsSeparatedByString:#"?"];
if([urlArray count] > 1)
{
NSString *paramsString = [urlArray lastObject];
NSString *cmd = #"";
NSArray *urlParamsArray = [paramsString componentsSeparatedByString:#"&"];
int numCommands = [urlParamsArray count];
NSMutableDictionary *paramsWithNames = [[NSMutableDictionary alloc ] initWithCapacity:numCommands];
for (NSString *elementPair in urlParamsArray)
{
NSArray *aPair = [elementPair componentsSeparatedByString:#"="];
NSString *aKey = [aPair objectAtIndex:0];
NSString *aParam = [aPair objectAtIndex:1];
if([aKey compare:#"_command"] == NSOrderedSame)
{
cmd = aParam;
}
else
{
[paramsWithNames setValue: aParam forKey:aKey];
}
}
[self executeCommand: cmd withParams: paramsWithNames view:theView webView:wView];
[paramsWithNames release];
}
}
This function get called by the following:
- (void)pullJSEvent:(NSTimer*)theTimer
{
NSLog(#"MainView - pullJSEvent()");
NSString *jsCall = [NSString stringWithString:#"if(typeof checkOBJCEvents == 'function'){checkOBJCEvents();}"];
NSString *jsAnswer = [[webView stringByEvaluatingJavaScriptFromString:jsCall] retain];
if([jsAnswer compare:#"none"] != NSOrderedSame)
{
//NSLog(#" answer => %#", jsAnswer);
[jsResponder checkRequest:jsAnswer view:(UIViewController *)self webView:self.webView];
}
[jsAnswer release];
}
Thank-you
You're going to have to dig a bit deeper with the Leaks instrument. You're leaking one of the strings in the array, not the array itself. Leaks indicates that line because that's where the strings in the array are allocated.
Go into Leaks, look at a leaked instance, and click that little arrow button. You'll see all the retains and releases of the leaked object, which should point you to the problem.

Initialization of an NSDictionary in Objective C (for iOS)

I am relatively new to Objective-C and now I have a problem in my iPhone app that I don't fully understand.
I try to use a NSMutableDictionary, this does not seem to work as i expect for some reason. When I run the debugger and do po numberToCallerMap to see the dictionary, I get an exception. I have read the documentation for NSMutableDictionary on how to initialize it, but I can not see what I am doing wrong. Help and advice are appreciated. The variable causing me problem is numberToCallerMap, here is the relevant function:
- (void)setData:(NSString*)value{
[list release];
list = [[NSMutableArray alloc] init];
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSMutableDictionary* numberToCallerMap;
CallerInfo* caller;
NSDictionary* callerInfo;
#try {
NSArray *array = (NSArray*)[json objectWithString:value];
// reading all the items in the array one by one
numberToCallerMap = [NSMutableDictionary dictionary];
for (id *item in array) {
// if the item is NSDictionary (in this case ... different json file will probably have a different class)
NSDictionary *dict2 = (NSDictionary *) item;
CallInfo *data = [CallInfo alloc];
[data initFromDictionary:dict2];
callerInfo = (NSDictionary*)[dict2 valueForKey:#"caller"] ;
//Here, we want the phonenumber to be part of the CallerInfo object instead.
// It is sent from the server as part of the Call-object
NSString* number = (NSString*)[dict2 valueForKey:#"phoneNumber"];
[callerInfo setValue:number forKey:#"phoneNumber"];
caller = (CallerInfo*)[numberToCallerMap valueForKey:number];
if(caller == nil || [caller isKindOfClass:[NSNull class]]){
caller = [CallerInfo alloc];
[caller initFromDictionary:callerInfo];
[numberToCallerMap setValue:caller forKey:number];
[list insertObject:caller atIndex:0];
}
[caller addRecentCall:data];
}
}
#catch (NSException * e) {
[list release];
list = [[NSMutableArray alloc] init];
}
#finally {
[numberToCallerMap release];
}
}
This is probably not the only problem, but you are not alloc-ing your numberToCallerMap dictionary, you are getting it from a convenience class method -- [NSMutableDictionary dictionary] -- that returns it autoreleased. So you should not call release on it yourself.

Objective C - UITableView after calling reloadData my object properties are null/nil

I have a ViewController defined as follows:
#interface SectionController : UITableViewController {
NSMutableArray *sections;
}
- (void) LoadSections;
When LoadSection is call it makes a call to NSURLConnection to load a url which in turn calls
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[connection release];
[responseData release];
NSDictionary *results = [responseString JSONValue];
NSMutableArray *jSections = [results objectForKey:#"Items"];
sections = [NSMutableArray array];
for (NSArray* jSection in jSections)
{
Section* section = [Section alloc];
section.Id = [jSection objectForKey:#"Id"];
section.Description = [jSection objectForKey:#"Description"];
section.Image = [jSection objectForKey:#"Image"];
section.Parent = [jSection objectForKey:#"Parent"];
section.ProductCount = [jSection objectForKey:#"ProductCount"];
[sections addObject:section];
[section release];
}
[jSections release];
[results release];
[delegate sectionsLoaded];
[self.view reloadData];
}
The data parses correctly and I now have sections filled with many items.
Calling [self.view reloadData] forces a callback to the delegate method cellForRowAtIndexPath which should then present the data into the cell however its at this point that sections is now nil again.
Can someone please point out my mistake? I must admit I am a newbie to objective c and it probably a pointer issue. What is need to do is retain the value of sections after calling reloadData.
Many thanks.
Seeing the new code the problem is obvious:
sections = [NSMutableArray array];
should become
[sections release];
sections = [[NSMutableArray alloc] init];
note that the array does not become again "nil", is instead deallocated and you get an invalid reference, which might (should) generate a crash on dereferencing.
I suggest you to read some articles on reference counted memory management as it might be not obvious if you are new to Objective-C, and often leads to mistake (i.e: autorelease is not magic at all)
best way to avoid all memory leaks here is just simply use #property (nonatomic, retain) NSMutableArray *sections; by using property you can be sure that all men management works will be correctly managed by system. Just don't forget that property retains value when you doing setSections:, so that you need to pass autoreleased object here.
self.sections = [NSMutableArray array];
...
[self.sections addObject:section];
Also to avoid all problem try to make all objects which should live only in this method autorelease. Like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *results = [responseString JSONValue];
NSMutableArray *jSections = [results objectForKey:#"Items"];
self.sections = [NSMutableArray array];
for (NSArray* jSection in jSections) {
Section* section = [[[Section alloc] init] autorelease];
section.Id = [jSection objectForKey:#"Id"];
section.Description = [jSection objectForKey:#"Description"];
section.Image = [jSection objectForKey:#"Image"];
section.Parent = [jSection objectForKey:#"Parent"];
section.ProductCount = [jSection objectForKey:#"ProductCount"];
[self.sections addObject:section];
}
[delegate sectionsLoaded];
[self.view reloadData];
}
And also most of object you trying to release already autoreleased:
all params passed into your method shouldn't be released manually, check I think JSONValue also should returns autoreleased object and anything you getting by enumerating or by call objectForKey: