Sorting NSJSON arrays aren't working properly - sorting

In my json file I have a title, subtitle, and url.
I sort the title to set the items alphabetically, but the url isn't sorted with the title and I don't know why.
This is what i've done:
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSArray *arrayOfItems = [allDataDictionary objectForKey:#"items"];
for (NSDictionary *diction in arrayOfItems) {
NSString *titles = [diction objectForKey:#"title"];
NSString *station = [diction objectForKey:#"url"];
[jsonArray addObject:titles];
[jsonStations addObject:station];
// SORT JSON
NSArray *sortedArray;
sortedArray = [jsonArray sortedArrayUsingComparator:^NSComparisonResult(NSString *title1, NSString *title2)
{
if ([title1 compare:title2] > 0)
return NSOrderedDescending;
else
return NSOrderedAscending;
}];
[jsonArray setArray:sortedArray];
When I press the first item in the tableView, I get get the url from a total diffrent title.
What should I do to get the title to match the url in the tableView?

First of all this seems like a strange way of sorting, you should use a dictionary instead of 2 arrays otherwise things get messy very quickly.
Secondly you need to pass your sortedArray to the table instead of the jsonArray currently it seems to be just trying to reset the jsonArray.
I would create one method to handle it like this (I have stripped some of your sorting script to simplify this)
-(NSArray *)sortContentWithJSONData {
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSArray *arrayOfItems = [allDataDictionary objectForKey:#"items"];
NSArray *sortedArray = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:false];
NSMutableArray *outputArray = [[NSMutableArray alloc] init];;
for (NSDictionary *diction in arrayOfItems) {
NSString *titles = [diction objectForKey:#"title"];
NSString *station = [diction objectForKey:#"url"];
[outputArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:titles, #"title", station, #"station", nil]]
}
return [outputArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortedArray]];
}
Then you could set a global array and access it in your table view using the following...
NSArray *tableContent = [self sortContentWithJSONData];
Hope that clears things up a bit :)

Related

NSMutableDictionary entry corrupted after departing from the method where entry was added

I use setObject:forKey: to add an object of type Rresource to a NSMutableDictionary named: resourceLib.
Then I immediately look at what's actually in the dictionary and it's OK.
When I try to look at it again in another object's method, the proper key is present but a reference to a string property "url" cases a list of error messages including:
2016-09-28 11:32:42.636 testa[760:16697] -[__NSCFString url]: unrecognized selector sent to instance 0x600000456350
Rresource object is defined as:
#interface Rresource : NSObject
#property (nonatomic,strong) NSString* url;
#property (nonatomic,strong)NSMutableArray* resourceNotesArray;
#property(nonatomic,strong)NSString* name;
#property(nonatomic,strong)NSString* resourceUniqueID;
#property(nonatomic)BOOL isResourceDirty;
This method in a ViewController adds the Rresource to the NSMutableDictionary
-(void)saveResource
{
Rresource* resource = self.currentResource;
Rresource* temp;
if (resource)
{
if ( resource.isResourceDirty)
{
[self.model.resourceLib setObject:resource forKey:resource.resourceUniqueID];
temp = [self.model.resourceLib objectForKey:resource.resourceUniqueID];
}
}
}
Resource and temp contain identical info showing the info was added correctly.
In model's method the following causes the error message described above.
for (Rresource* resource in self.resourceLib)
{
NSString* string = resource.url;
}
where model contains:
#property(nonatomic,strong)NSMutableDictionary* resourceLib;
and :
#implementation Model
- (instancetype)init
{
self = [super init];
if (self)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
self.path = [[paths objectAtIndex:0] stringByAppendingString:#"/Application Support/E2"];
BOOL exists = [[NSFileManager defaultManager] createDirectoryAtPath:self.path withIntermediateDirectories:NO attributes:nil error:nil];
if (!exists)
{
[[NSFileManager defaultManager] createDirectoryAtPath:self.path withIntermediateDirectories:NO attributes:nil error:nil];
}
self.resourceLibPath = [NSString pathWithComponents:#[self.path,#"resources"]];
self.resourceLib = [[NSMutableDictionary alloc]init];
self.noteLibPath = [NSString pathWithComponents:#[self.path, #"notes"]];
self.noteLib = [[NSMutableDictionary alloc]init];
}
return self;
I have found this question difficult to ask clearly even after spending several hours formulating it. I apologize.
I've tried pretty much everything for about a week. I'm stumped.
Any ideas?
Thanks
According to this entry on Enumeration, when you iterate over a dictionary using the fast enumeration syntax, you're iterating over its keys. In the above code sample you're assuming the enumeration happens over its values. What you're effectively doing is casting an NSString object as an Rresource, and sending it a selector only an actual Rresource object can respond to.
This should fix the loop:
for (NSString* key in self.resourceLib)
{
NSString* string = [self.resourceLib objectForKey:key].url;
}

contacts data in xcode

Everybody..
I am trying to develop an app for iPhone which basicly deals with ABAddressBook and contacts data...
My goal is to send all the contacts data (at first step, names and phones) in a file by e-mail..
Now, I am trying to reach the contacts data, and I want to add them to two different arrays, names and phones arrays..
At first, I am trying to see all the datas in the screen when i pressed a button "List Contacts". the datas should be seen on the screen. and then when i pressed the second button "Send Contacts", it should send the file to an e-mail account.. This is how my apps will work..
I am having problems at showing the data on the screen.. I wrote something but it doesn't give anything on the screen in a textView..
Can you help me to solve this problem?
Here's the code for listing the contacts (listCon method):
-(IBAction)listCon:(id)sender
{
NSMutableArray *names = [[NSMutableArray alloc] init];
NSMutableArray *numbers1= [[NSMutableArray array] init];
NSMutableArray *numbers2= [[NSMutableArray array] init];
NSMutableArray *numbers3= [[NSMutableArray array] init];
ABAddressBookRef addressBook = ABAddressBookCreate();
if (addressBook != nil)
{
NSLog(#"Successfully accessed the address book.");
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople= ABAddressBookGetPersonCount(addressBook);
NSUInteger peopleCounter = 0;
for (peopleCounter = 0;peopleCounter < nPeople; peopleCounter++)
{
ABRecordRef thisPerson = CFArrayGetValueAtIndex(allPeople,peopleCounter);
NSString *contactFirstLast = [NSString stringWithFormat:#"%,%",ABRecordCopyValue(thisPerson, kABPersonFirstNameProperty), ABRecordCopyValue(thisPerson,kABPersonLastNameProperty)];
[names insertObject:contactFirstLast atIndex:peopleCounter];
ABMultiValueRef phoneNumbers = ABRecordCopyValue(thisPerson,kABPersonPhoneProperty);
NSString *firstPhone = (__bridge_transfer NSString*) ABMultiValueCopyValueAtIndex(phoneNumbers, 0);
NSString *secondPhone = (__bridge_transfer NSString*) ABMultiValueCopyValueAtIndex(phoneNumbers, 1);
NSString *thirdPhone = (__bridge_transfer NSString*) ABMultiValueCopyValueAtIndex(phoneNumbers, 2);
[numbers1 insertObject:firstPhone atIndex:peopleCounter];
[numbers2 insertObject:secondPhone atIndex:peopleCounter];
[numbers3 insertObject:thirdPhone atIndex:peopleCounter];
}
}
myView.text=[names componentsJoinedByString:#"\n\n"];
myView.text=[numbers1 componentsJoinedByString:#"\n\n"];
myView.text=[numbers2 componentsJoinedByString:#"\n\n"];
myView.text=[numbers3 componentsJoinedByString:#"\n\n"];
}
Just glancing at your code, you can't do this:
NSString *contactFirstLast = [NSString stringWithFormat:#"%,%",ABRecordCopyValue(thisPerson, kABPersonFirstNameProperty), ABRecordCopyValue(thisPerson,kABPersonLastNameProperty)];
There are several errors: first off % in your stringWithFormat: is not a format specifier; you probably are thinking of %#. Second off, copying the value of kABPersonFirstNameProperty will return a CFStringRef, and that's not what you want to display the name in a text field. You'll have to toll-free bridge the result of ABRecordCopyValue(). You can do this by adding this line - (__bridge_transfer NSString *) - in front of your ABRecordCopyValue()'s. With all the corrections, it should look like this:
NSString *contactFirstLast = [NSString stringWithFormat:#"%#,%#", (__bridge_transfer NSString *)ABRecordCopyValue(thisPerson, kABPersonFirstNameProperty), (__bridge_transfer NSString *)ABRecordCopyValue(thisPerson,kABPersonLastNameProperty)];
Hope this help (might not cover all errors)!

copy and paste text + image from NSPasteboard

we are working on a C++ Qt applciation that copies selected text and/or images from external applications, modifies it and then paste it back. Since we are on Mac, we are doing this part with Objective-C.
We are having problems trying to get an image from the Pasteboard. It works fine for text, but we are not sure about how to handle images or combination of text+image.
Since we dont know what the user might select, we should be able to perform a generic retrieval of content of the pasteboard to modify it and putting it back in the pasteboard.
We've try this:
//we thought about retrieving some generic item from pasteboard, using NSPasteboardItem
NSArray *classes = [[NSArray alloc] initWithObjects:[NSPasteboardItem class], nil];
NSDictionary *options = [NSDictionary dictionary];
NSArray *auxArray = [[NSPasteboard generalPasteboard] readObjectsForClasses:classes options:options];
NSData *archived_data = [auxArray objectAtIndex:0];
Our solution for handling text was:
NSString *text = [[NSPasteoard generalPasteboard] stringForType:NSStringPboardType];
string text_str = string([text UTF8String]);
It didnt work, so, How can we get the user selection from the pasteboard?
We need to get the raw bytes or rtf content in order to modify it as we need, and then putting it back in the pasteboard and paste it back replacing the original user selection.
Thanks!
I think this function will help you
- (IBAction)paste:sender
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *classArray = [NSArray arrayWithObject:[NSImage class]];
NSDictionary *options = [NSDictionary dictionary];
BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
if (ok)
{
NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
NSImage *image = [objectsToPaste objectAtIndex:0];
[imageView setImage:image];
}
}

Cocoa Core Data: Setting default entity property values?

I know I can set default values either in the datamodel, or in the -awakeFromInsert method of the entity class. For example, to make a "date" property default to the current date:
- (void) awakeFromInsert
{
NSDate *now = [NSDate date];
self.date = now;
}
How though can I make an "idNumber" property default to one greater than the previous object's idNumber?
Thanks, Oli
EDIT: Relevant code for my attempt (now corrected)
- (void) awakeFromInsert
{
self.idNumber = [NSNumber numberWithInt:[self maxIdNumber] + 1];
}
-(int)maxIdNumber{
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Flight" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idNumber > %#", [NSNumber numberWithInt:0]];
[request setPredicate:predicate];
[request setFetchLimit:1];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"idNumber" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil | array.count == 0)
{
return 0;
}
return [[[array objectAtIndex:0] valueForKey:#"idNumber"] intValue];
}
If the maxIdNumber method is called, the new object is added to the table twice!? (but with the correct idNumber). The two entries in the table are linked - editing / removing one also edits / removes the other. For this reason I believe it has something to do with the managed object context. For what its worth, the outcome (two copies) is the same no matter how many times the maxIdNumber method is called in the awakFromNib; even if self.idNumber is just set to [NSNumber numberWithInt:5] and the maxIdNumber method is just called for a throwaway variable.
Any clues??
SOLVED IT!
Ok, the problem of double entry occurs when a fetch request is performed from within the awakeFromInsert method. Quoting from the docs:
You are typically discouraged from performing fetches within an implementation of awakeFromInsert. Although it is allowed, execution of the fetch request can trigger the sending of internal Core Data notifications which may have unwanted side-effects. For example, on Mac OS X, an instance of NSArrayController may end up inserting a new object into its content array twice.
A way to get around it is to use the perfromSelector:withObject:afterDelay method as outlined here (I am only allowed to post one hyperlink :( ):http://www.cocoabuilder.com/archive/cocoa/232606-auto-incrementing-integer-attribute-in-awakefrominsert.html.
My working code is now as follows: (note, I have put the bulk of the fetching code used above into a category to tidy it up a little, this allows me to use the method fetchObjectsForEntityName:withPredicate:withFetchLimit:withSortDescriptors:)
- (void) awakeFromInsert
{
[self performSelector:#selector(setIdNumber) withObject:nil afterDelay:0];
self.date = [NSDate date];
}
-(void)setIdNumber
{
int num = 0;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"idNumber" ascending:NO];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idNumber > %#", [NSNumber numberWithInt:0]];
NSArray *array = [[self managedObjectContext] fetchObjectsForEntityName:#"Flight"
withPredicate:predicate
withFetchLimit:0
withSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
if (array != nil & array.count != 0)
{
num = [[[array objectAtIndex:0] valueForKey:#"idNumber"] intValue];
}
num ++;
[self setIdNumber:[NSNumber numberWithInt:num]];
}
Let me know what you think!
One Approach: Create a fetch request of all instances of your entity with a limit of 1, sorted by idNumber to get the highest number.
Another Approach: Keep the highest idNumber in your store's metadata and keep incrementing it.
There are plenty of arguments for and against either. Ultimately, those are the two most common and the choice is yours.
An easier way to do that is to override the newObject method of NSArrayController:
- (id) newObject
{
id result=[super newObject];
[result setValue: [NSDate date] forKey: #"date"];
return result;
}

Memory problems with NSMutableDictionary, causing NSCFDictionary memory leaks

Help me please with the following problem:
- (NSDictionary *)getGamesList
{
NSMutableDictionary *gamesDictionary = [[NSMutableDictionary dictionary] retain];
// I was trying to change this on the commented code below, but did have no effect
// NSMutableDictionary *gamesDictionary = [[NSMutableDictionary alloc] init];
// [gamesDictionary retain];
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSArray *gameDate = [key componentsSeparatedByString:#" "];
NSNumber *_id = [[NSNumber alloc] initWithInt:sqlite3_column_int(statement, 0)];
NSString *date_time = [NSString stringWithFormat:#"%#, %#",[gameDate objectAtIndex:0],[gameDate objectAtIndex:2]];
if (![gamesDictionary valueForKey:date_time]) [gamesDictionary setValue:[NSMutableArray array] forKey:date_time];
[[gamesDictionary valueForKey:date_time] addObject:[[_id copy] autorelease]];
[_id release];
}
sqlite3_reset(statement);
return gamesDictionary;
}
The leak starts in another method of another class, there the getGamesList method is called, like this:
NSMutableDictionary *gamesDictionary;
gamesDictionary = [[NSMutableDictionary dictionaryWithDictionary:[appDelegate getGamesList]] retain];
After that there are a lot of leaks that points to NSCFArray in the string:
NSArray *keys = [[NSArray arrayWithArray:[gamesDictionary allKeys]] retain];
in this method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *keys = [[NSArray arrayWithArray:[gamesDictionary allKeys]] retain];
if ([keys count] != 0) return [[keys objectAtIndex:section] uppercaseString];
return #"";
}
I assume these things are connected to each other, but I still can not understand all of the memory management tips.
Thanks a lot!
Didn't use Cocoa for years (that's why I can't tell you an exact answer :/). But I guess your problem is that you systematically use retain on your objects.
Since the object reference count never get to 0, all dictionaries are keep in memory and not freed.
Try to remove the retain on [NSArray arrayWithArray] and [NSMutableDictionary dictionaryWithDictionary
http://en.wikibooks.org/wiki/Programming_Mac_OS_X_with_Cocoa_for_beginners/Some_Cocoa_essential_principles#Retain_and_Release
It does look like you are over-retaining your array.
When you create the gamesDictionary it is created with an retain count of +1. You then retain it (count becomes +2). When you get the value outside this function you retain again (count becomes +3).
You are correct that if you create an object you are responsible for it's memory management. Also, when you get an object from a method, you should retain it if you want to keep it around for longer than the span of the function. In your case, you just want to get at some of the properties of the object, so you don't need to retain it.
Here is a suggestion:
- (NSDictionary *)getGamesList
{
NSMutableDictionary *gamesDictionary = [NSMutableDictionary dictionary]; // Remove the retain.
while (sqlite3_step(statement) == SQLITE_ROW)
{
NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSArray *gameDate = [key componentsSeparatedByString:#" "];
NSNumber *_id = [[NSNumber alloc] initWithInt:sqlite3_column_int(statement, 0)];
NSString *date_time = [NSString stringWithFormat:#"%#, %#",[gameDate objectAtIndex:0],[gameDate objectAtIndex:2]];
if (![gamesDictionary valueForKey:date_time]) [gamesDictionary setValue:[NSMutableArray array] forKey:date_time];
[[gamesDictionary valueForKey:date_time] addObject:[[_id copy] autorelease]];
[_id release];
}
sqlite3_reset(statement);
return gamesDictionary;
}
This next bit is messy. you create a new dictionary and retain it. The original dictionary is not autoreleased, so the count isn't decremented and it always hangs around. Just assign the dictionary rather than create a new one.
NSMutableDictionary *gamesDictionary = [[appDelegate getGamesList] retain];
// Retaining it, becuase it looks like it's used elsewhere.
Now, in this method:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *returnString;
// Don't need to retain the keys because you are only using it within the function
// and since you didn't alloc, copy or retain the array it contains, you aren't responsible for it's memory management.
NSArray *keys = [NSArray arrayWithArray:[gamesDictionary allKeys]];
if ([keys count] != 0) {
returnString = [[NSString alloc] initWithString:[[keys objectAtIndex:section] uppercaseString]];
return [returnString autorelease];
}
return #"";
}

Resources