I used this answer to sort 2 objects by date, and it worked perfectly: Get one NSArray
I now need to sort 3 objects by date, and can't quite modify what I have to get that right.
All of the Articles from the API/RSS feeds will be sorted by date in 1 tableView.
Here's what I tried:
- (void)sortCombinedModel {
// All 3
[self.combinedModel sortUsingComparator:^NSComparisonResult(id a, id b, id c) {
NSDate *dateA, *dateB, *dateC;
dateA = ([a isKindOfClass:[FeedRSS self]])? ((FeedRSS *)a).pubDate : ((Data *)a).created_time : ((YaRSS *)a).pubDate;
dateB = ([b isKindOfClass:[FeedRSS self]])? ((FeedRSS *)b).pubDate : ((Data *)b).created_time : ((YaRSS *)b).pubDate;
dateC = ([c isKindOfClass:[FeedRSS self]])? ((FeedRSS *)c).pubDate : ((Data *)c).created_time : ((YaRSS *)c).pubDate;
return [dateB compare:dateA compare:dateC];
}];
}
Can you help me sort the 3 dates?
Additional info if needed:
I figured out how to modify this part - Have 3 API/RSS feeds coming in to one NSMutableArray:
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
// Here's the #1
[self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
// Here's the trick. call API2 here. Doing so will serialize these two requests
[self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
// Here's the trick. call API3 here. Doing so will serialize these two requests
[self loadThreeWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
[self sortCombinedModel];
[self.tableView reloadData];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
}
Here's what sorting 2 dates looked like previously:
- (void)sortCombinedModel {
[self.combinedModel sortUsingComparator:^NSComparisonResult(id a, id b) {
NSDate *dateA, *dateB;
dateA = ([a isKindOfClass:[Feed self]])? ((Feed *)a).published : ((Data *)a).created_time;
dateB = ([b isKindOfClass:[Feed self]])? ((Feed *)b).published : ((Data *)b).created_time;
return [dateA compare:dateB];
}];
}
The comparator must always take two values to compare, but it wants to check if those two values are one of three types. Add the following to the public interfaces defined in FeedRSS.h, Data.h and YaRSS.h:
- (NSDate *)sortDate;
In each of the implementations, add a method that returns the right date property to sort on for the class, so, e.g.
// FeedRSS.m
- (NSDate *)sortDate {
return self.pubDate;
}
Same idea for Data.m (return self.created_time), and same for YaRSS.h, return whatever date that object has that you want to sort on. Now your comparator is like this:
- (void)sortCombinedModel {
[self.combinedModel sortUsingComparator:^NSComparisonResult(id a, id b) {
NSDate *dateA = nil, *dateB = nil;
if ([a isKindOfClass:[Feed self]]) { dateA = ((Feed *)a).sortDate; }
else if ([a isKindOfClass:[Data self]]) { dateA = ((Data *)a).sortDate; }
else if ([a isKindOfClass:[YaRSS self]]) { dateA = ((YaRSS *)a).sortDate; }
if ([b isKindOfClass:[Feed self]]) { dateB = ((Feed *)b).sortDate; }
else if ([b isKindOfClass:[Data self]]) { dateB = ((Data *)b).sortDate; }
else if ([b isKindOfClass:[YaRSS self]]) { dateB = ((YaRSS *)b).sortDate; }
return [dateA compare:dateB];
}];
}
This works if the array contains only the three kinds of objects you expect. If you always want to sort this way, an even tidier approach is to implement compare: in each of those classes, in each one checking if the param is one of the other two types.
Related
I'm trying to check if NSString 'testing' (47) exists inside of my NSMutableArray 'self.checkfriendData'. I'm using the code below, though after logging my if statement it appears as though it's never executed (even though the statement is true - see console data below, uid = 47, and thus hiding my object should fire?) Any idea as to why this isn't working? Help is much appreciated!
ViewController.m
NSMutableDictionary *viewParams3 = [NSMutableDictionary new];
[viewParams3 setValue:#"accepted_friends" forKey:#"view_name"];
[DIOSView viewGet:viewParams3 success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.checkfriendData = (NSMutableArray *)responseObject;
NSString *testing = #"47";
NSArray *friendorNo = self.checkfriendData;
if ([friendorNo containsObject:testing]) // YES
{
self.addFriend.hidden = YES;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
Here's what's inside self.checkfriendData:
2017-05-18 19:36:07.266529-0700 This is the friend data check (
{
body = "My name is Britt";
friendphoto = "/sites/default/files/stored/x.jpg";
"node_title" = "Britt";
uid = 47;
}
)
It appears that your NSArray contains NSDictionarys and you are asking if the array contains an NSString. The answer will always be no as the array doesn't directly contain any NSStrings.
If you want to search for the uid of 47 you will have to iterate over the array and check the uid key of each NSDictionary for the value 47.
The code for this would look something like:
for (NSDictionary *dict in friendorNo) {
if ([dict[#"uid"] isEqualToString:testing]) {
self.addFriend.hidden = YES;
}
}
Im newly using AFNetworking to get JSON and parse it,I've imported it to my project and got the JSON but i don't know how is it possible to parse the JSON for display especially in Objective-c.
Here is the code in my viewDidLoad to Get JSON :
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://api.androidhive.info/json/movies.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
The Retrieved JSON for parsing:
{
genre = (
Action,
Drama,
"Sci-Fi"
);
image = "http://api.androidhive.info/json/movies/1.jpg";
rating = "8.300000000000001";
releaseYear = 2014;
title = "Dawn of the Planet of the Apes";
},
{
genre = (
Action,
"Sci-Fi",
Thriller
);
image = "http://api.androidhive.info/json/movies/4.jpg";
rating = "8.4";
releaseYear = 2014;
title = "X-Men: Days of Future Past";
},
{
genre = (
Action,
Adventure,
Fantasy
);
image = "http://api.androidhive.info/json/movies/7.jpg";
rating = "7.3";
releaseYear = 2014;
title = "The Amazing Spider-Man 2";
},
{
genre = (
Animation,
Comedy,
Family
);
image = "http://api.androidhive.info/json/movies/9.jpg";
rating = "8.300000000000001";
releaseYear = 2013;
title = Rush;
},
{
genre = (
Animation,
Adventure,
Family
);
image = "http://api.androidhive.info/json/movies/15.jpg";
rating = "8.199999999999999";
releaseYear = 2010;
title = "How to Train Your Dragon";
}
)
Update :
How is it possible to display it and store the retrieved data so its easier to show it in UITableView.
i tried to do like :
NSLog(#"JSON: %#", [responseObject objectForKey:#"image"]);
to get the whole images only but it crashes. i just need to get for example all the titles and store them in array and then display them UITableView.
you try this way to get data from NSDictionary
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://api.androidhive.info/json/movies.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
self.arrData=[[NSMutableArray alloc]initWithArray:responseObject];
[self.tableView reloadData];// reload table data
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
TableView Delegate Method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// your cell code here
// indexPath.row(use in tableView) means your number of index to get value
NSLog(#"JSON: %#", [[self.arrData objectAtIndex:indexPath.row] objectForKey:#"image"]);
NSLog(#"title: %#", [[self.arrData objectAtIndex:indexPath.row] objectForKey:#"title"]);
NSLog(#"rating: %#", [[self.arrData objectAtIndex:indexPath.row] objectForKey:#"rating"]);
NSLog(#"releaseYear: %#", [[self.arrData objectAtIndex:indexPath.row] objectForKey:#"releaseYear"]);
NSLog(#"genre: %#", [[self.arrData objectAtIndex:indexPath.row] objectForKey:#"genre"]);// return array get value particular using array index
}
AFNetworking returns already processed JSON in responseObject - and in your case it's an NSArray of NSDictionary objects inside it.
To access a single NSDictionary create an NSArray property (e.g. resultArray) and set it to responseObject, then you can access its objects by resultArray[1] (make sure to bounce check).
Then you can access the values of the dictionary by their keys (myDictionary[#"title] and so on).
Managed to cobble together a CKFetchRecordsOperation after much searching for sample code; and here it is... but I must have missed something. Don't get me wrong it works a treat... but...
To execute a CKFetchRecordsOperation you need an NSArray of CKRecordIDs; to get a NSArray of CKRecordIDs, you need to execute CKQuery thru which you can build your NSArray of CKRecordIDs.
But wait a minute, the process of extracting the CKRecordIDs uses a CKQuery, thru which I could simply download the CKRecords anyway?
How do you get your NSArray of CKRecordIDs if not with a CKQuery?
-(void)doSingleBeaconsDownload
{
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"iBeaconConfig = %#",iBeaconsConfirmed.giReferenceID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Running" predicate:predicatex];
[self.delegate performSelectorOnMainThread:#selector(processCompleted:) withObject:#"Downloading Configuration ..." waitUntilDone:YES];
[publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if (error) {
NSLog(#"Batch Download Error iCloud error %#",error);
}
else {
NSMutableArray *rex2download = [[NSMutableArray alloc] init];
for (CKRecord *rex in results) {
[rex2download addObject:rex.recordID];
}
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:rex2download];
/* fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
if (error) {
// Retain the record IDs for failed fetches
}
else {
// Do something with each record downloaded
}
};*/
fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded) {
if (error) {
// damn ...
} else {
NSLog(#"Downloaded %f", recordsDownloaded);
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
if (error) {
// Failed to fetch all or some of the records
}
else {
for(CKRecord *record in results) {
NSLog(#"Fini download %lu",(unsigned long)[recordsByRecordID count]);
}
[self.delegate performSelectorOnMainThread:#selector(beaconsDownloaded:) withObject:noOf waitUntilDone:YES];
}
};
[publicDatabase addOperation:fetchRecordsOperation];
}
}];
}
From Apple Documentation: A CKFetchRecordsOperation object retrieves CKRecord objects (whose IDs you already know) from iCloud.
A CKQueryOperation is used to retrieve CKRecords from iCloud based on some Query, so you can get them even if you do not know their recordIDs. A CKFetchRecordsOperation is used ONLY when you have the CKRecordIDs. You can create a CKRecordID without accessing iCloud, and you can store them in any local storage you have.
A good use case, which I use for this kind of operation, is when you want to modify a CKRecord, you need to first retrieve it from iCloud (using CKFetchRecordsOperation) and then save it back using CKModifyRecordsOperation.
Have a look at the two WWDC 2014 Videos on CloudKit that explain this pretty well.
Thanks for your help! I managed to craft an CKQueryOperation into the code, but ... but my code is soon going be become unreadable with many more of these nested loops? Surely there is a more elegant way to link CKQuery/Fetch/Modify operations; tried dependancies but missing something still ?
-(void)doSingleBeaconsDownload
{
[self.delegate performSelectorOnMainThread:#selector(processCompleted:) withObject:#"Downloading Configuration ..." waitUntilDone:YES];
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"iBeaconConfig = %#",iBeaconsConfirmed.giReferenceID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Running" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.resultsLimit = CKQueryOperationMaximumResults;
NSMutableArray *rex2download = [[NSMutableArray alloc] init];
queryOp.recordFetchedBlock = ^(CKRecord *results)
{
[rex2download addObject:results.recordID];
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
// Cursor it seems contains a reference to a second call to it [required] if you try download more then 100 records
if (error) {
NSLog(#"Batch Download Error iCloud error %#",error);
}
else {
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:rex2download];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
if (error) {
// Retain the record IDs for failed fetches
}
else {
// Do something ..
}
};
fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded) {
if (error) {
// damn
} else {
NSLog(#"Downloaded X");
}
};
fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
if (error) {
// Failed to fetch all or some of the records
}
else {
… Final clean up
}
};
[publicDatabase addOperation:fetchRecordsOperation];
}
};
[publicDatabase addOperation:queryOp];
}
Thanks Harry; Here is my third and final working solution; used a singleton global variable to pass the data between the two CKQueryOperations; don't know if that is best/good practice, but it works
... seems a pity you cannot use something like this ...
[fetchRecordsOperation addDependency:queryOp]; &
[queue fetchRecordsOperation]; (doesn't compile)
Would be a far cleaner solution... anyway here is V3 for completeness..
-(void)doSingleBeaconsDownloadV3
{
NSLog(#"doQuery executing");
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"iBeaconConfig = %#",iBeaconsConfirmed.giReferenceID];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Running" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.resultsLimit = CKQueryOperationMaximumResults;
//NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO];
//query.sortDescriptors = #[sortDescriptor];
queryOp.recordFetchedBlock = ^(CKRecord *results)
{
[iBeaconsConfirmed.giRex2Download addObject:results.recordID];
NSLog(#"fetched %lu",[iBeaconsConfirmed.giRex2Download count]);
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
if (error) {
NSLog(#"Batch Download Error iCloud error %#",error);
} else {
NSLog(#"fetched %lu",[iBeaconsConfirmed.giRex2Download count]);
[self DoFetchV2];
}
};
[publicDatabase addOperation:queryOp];
}
-(void)DoFetchV2
{
NSLog(#"dofetch executing %lu",[iBeaconsConfirmed.giRex2Download count]);
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:iBeaconsConfirmed.giRex2Download];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
if (error) {
// Retain the record IDs for failed fetches
}
else {
// Do something useful with data
}
};
fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded)
{
NSLog(#"Downloaded X");
};
fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
if (error) {
// Failed to fetch all or some of the records
} else {
NSLog(#"Fini download %lu",(unsigned long)[recordsByRecordID count]);
}
};
[publicDatabase addOperation:fetchRecordsOperation];
}
I have the following code where I am calling self.tableView reloadData inside the loop which is a bad idea. If I put it outside the loop then it would not work as expected since it will be called before updating the noOfGroceryItems field.
How can I improve it?
-(void) populateShoppingLists {
[_groceriesService getAllShoppingLists:^(NSArray *results, NSError *error) {
if(error) {
NSLog(#"%#",error.localizedDescription);
}
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
// get the number of grocery items in the shopping list
[_groceriesService getItemsByShoppingList:shoppingList result:^(NSArray *results, NSError *error) {
shoppingList.noOfGroceryItems = results.count;
[_shoppingLists addObject:shoppingList];
// THIS IS BAD IDEA
**dispatch_async(dispatch_get_main_queue(), ^{
_shoppingLists = [_shoppingLists sort:#"title" ascending:YES];
[self.tableView reloadData];
});**
}];
}
}];
}
Implementation for getItemsShoppingList method:
-(void) getItemsByShoppingList:(ShoppingList *)shoppingList result:(GetItemsByShoppingList) getItemsByShoppingList
{
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"GroceryItems" predicate:[NSPredicate predicateWithFormat:#"ShoppingList == %#",shoppingList.record]];
[_privateDB performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
getItemsByShoppingList(results,error);
}];
}
You could add a conditional to reload the table if and only if the last shopping list has been fetched, ex:
[_groceriesService getAllShoppingLists:^(NSArray *results, NSError *error) {
if(error) {
NSLog(#"%#",error.localizedDescription);
}
// Variable to count the number
// of records processed
__block int recordsProcessed = 0;
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
// (I've changed this second results variable from results
// to results2 in order to distinguish between the two
// "results" variables.)
// get the number of grocery items in the shopping list
[_groceriesService getItemsByShoppingList:shoppingList
result:^(NSArray *results2, NSError *error) {
shoppingList.noOfGroceryItems = results2.count;
[_shoppingLists addObject:shoppingList];
dispatch_async(dispatch_get_main_queue(), ^{
_shoppingLists = [_shoppingLists sort:#"title" ascending:YES];
// Increment recordsProcessed to indicate another
// record has been processed
recordsProcessed ++;
// If all the records have been processed,
// reload the table (using the outer block's
// results variable, not the inner block's result2
// variable).
if (recordsProcessed == results.count) {
[self.tableView reloadData];
}
});
}];
}
}];
Update: And a second solution. Assuming that _shoppingLists starts out empty, you can simply compare results with the number of elements put into _shoppingLists, ex:
[_groceriesService getAllShoppingLists:^(NSArray *results, NSError *error) {
if(error) {
NSLog(#"%#",error.localizedDescription);
}
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
// (I've changed this second results variable from results
// to results2 in order to distinguish between the two
// "results" variables.)
// get the number of grocery items in the shopping list
[_groceriesService getItemsByShoppingList:shoppingList
result:^(NSArray *results2, NSError *error) {
shoppingList.noOfGroceryItems = results2.count;
[_shoppingLists addObject:shoppingList];
dispatch_async(dispatch_get_main_queue(), ^{
_shoppingLists = [_shoppingLists sort:#"title" ascending:YES];
// If the number of shopping lists stored
// equals the number of records processed,
// reload the table (using the outer block's
// results variable, not the inner block's result2
// variable).
if (_shoppingLists.count == results.count) {
[self.tableView reloadData];
}
});
}];
}
}];
If you put a dispatch_async after the for loop, you're guaranteed that the block will be dispatched on the main queue, after the loops completes (and your data is updated).
Why not just dispatch the reloadData call once after all your model are processed and updated?
Instead of fast enumerating the results array, you could enumerate with a block, that gives you the index of the current object. if the index is [results count] - 1, trigger the reload.
Of course you should not name two arrays results if the inner one does hide the outer, especially if you intend to call other peoples code «not a very good solution.»
While working on an iOS app, I am having issue resolving the properties returned from a NSFetchRequest. This is not about setting the resultType or the propertiesToFetch. It is about using the NSArray of NSDictionary instances.
Here is the actual code below, the crash is near the bottom. Thank you!
BTW, the point of this code is to eventually produce a list of section headers based on hair color (that is not under hats) and then produce a list of people, without hats who have that hair color for the cells. I am not sure this is the right approach to do that, but regardless, the question stands. Thanks Again!
//
// CDHairbrained.m
//
#import "CDHairbrained.h"
#import "CDHair.h"
#import "CDPerson.h"
#implementation CDHairbrained
void defaultErrorBlock(NSError*error) {
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
} else {
NSLog(#" %#", [error userInfo]);
}
UIAlertView* av = [[UIAlertView alloc] initWithTitle:#"Booo..." message:#"MangagedObjectContext Error" delegate:nil cancelButtonTitle:#"cry" otherButtonTitles: nil];
[av show];
}
-(void) initializeObjectContext:(NSManagedObjectContext*)moc {
//NSArray<CDHairs>
for (CDHair *hair in [self fetchAllOfEntityName:#"Hair" InManagedObjectContext:moc]) {
[moc deleteObject:hair];
}
for (CDPerson *person in [self fetchAllOfEntityName:#"Person" InManagedObjectContext:moc]) {
[moc deleteObject:person];
}
//NSDictionary{color}
NSDictionary* hairdata = #{#"red": [NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"blond":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"brown":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"black":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc]
};
for (NSString* color in hairdata.allKeys) {
CDHair* hair = hairdata[color];
hair.color = color;
}
//NSArray<NSDictionary{name,hair,hat}>
NSArray* peopleData = #[
#{#"name":#"Stan",#"hair":hairdata[#"red"], #"hat":#"no"},
#{#"name":#"Lucy",#"hair":hairdata[#"red"], #"hat":#"no"},
#{#"name":#"Fred",#"hair":hairdata[#"black"], #"hat":#"no"},
#{#"name":#"Sherlock",#"hair":hairdata[#"black"], #"hat":#"yes"},
#{#"name":#"Barney",#"hair":hairdata[#"blond"], #"hat":#"yes"},
#{#"name":#"Dennis",#"hair":hairdata[#"blond"], #"hat":#"yes"}
];
for (NSDictionary* personData in peopleData) {
CDPerson* person =[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:moc];
person.name = personData[#"name"];
person.hair = personData[#"hair"];
person.hat = personData[#"hat"];
}
NSError*error;
[moc save:&error];
if(error) defaultErrorBlock(error);
}
-(NSArray*) fetchAllOfEntityName:(NSString*)entityName InManagedObjectContext:(NSManagedObjectContext*) moc {
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:entityName];
NSError* error;
NSArray* fetchResults = [moc executeFetchRequest:request error:&error];
if (fetchResults) {
return fetchResults;
}
defaultErrorBlock(error);
return nil;
}
-(NSArray*) fetchDistinctProperties:(NSArray*) propertyDescriptors
forEntityName:(NSString*) entityName
Predicate:(NSPredicate*) predicate
SortedBy:(NSArray*) sortDescriptors
InManagedObjectContext:(NSManagedObjectContext*)moc
FailureBlock:(void(^)(NSError*)) failureBlock
{
// The darnedest thing: you can't query disctict against in memory changes.
// CoreData is more trouble than it is worth.
if (moc.hasChanges) {
[NSException raise:#"FetchDistinct not return in memory changes." format:#"%# has unsaved changes.",moc];
}
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
fetchRequest.returnsDistinctResults = YES;
fetchRequest.propertiesToFetch = propertyDescriptors;
fetchRequest.resultType =NSDictionaryResultType;
fetchRequest.predicate=predicate;
fetchRequest.sortDescriptors = sortDescriptors;
NSError* error;
NSArray* fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
if (fetchResults) {
NSLog(#"Fetched %3lu properties of %#", (unsigned long)fetchResults.count, entityName );
return fetchResults;
}
if (failureBlock)
failureBlock(error);
else
defaultErrorBlock(error);
return nil;
}
-(void) doIt:(NSManagedObjectContext*)moc {
[self initializeObjectContext:moc];
// Get a list of distinct Hair that is not underhats, to be section headers.
// And Get a list of People, with that Hair and without hats to be section cells.
//
// Expecting visibleHair to contain red, black. Not blond (none visible) Not brown, no people w/ brown hair.
// Get a distinct list of hair properties from all people without hats.
// Presume result is NSArray*<NSDictionary*{"hair":CDHair*}>
NSArray* visibleHair = [self fetchDistinctProperties:#[#"hair"]
forEntityName:#"Person"
Predicate:[NSPredicate predicateWithFormat:#"hat=='no'"]
SortedBy:nil
InManagedObjectContext:moc
FailureBlock:nil
];
// Quick Sanity Check for the debugger
NSDictionary* foundProperties = [visibleHair firstObject];
CDHair* aFoundHair = foundProperties[#"hair"];
NSLog(#"%u",aFoundHair.isFault); // <--- is nil
NSLog(#"aFoundHair: %#",aFoundHair);
NSLog(#"aFoundHair: %#",aFoundHair.color); // <------ CRASH!
// 2013-11-06 12:43:19.513 CDTest[2865:70b] -[_NSObjectID_48_0 color]: unrecognized selector sent to instance 0x8ba8670
NSLog(#"aFoundHair: %#",aFoundHair);
// Get a list of people with a particular Hair Color, who don't have hats.
NSSet* peopleWithAFoundHair = aFoundHair.people; // of CDPerson
NSArray* peopleWithAFoundHairSorted=[peopleWithAFoundHair sortedArrayUsingDescriptors:
[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]
]; // of CDPerson
NSArray*peopleWithAFoundVisibleHairSorted = [peopleWithAFoundHairSorted filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings)
{
CDPerson*p=evaluatedObject;
return [p.hat compare:#"no"]==NSOrderedSame;
}]
]; // of CDPerson
CDPerson* aPerson=[peopleWithAFoundVisibleHairSorted firstObject];
NSLog(#"%#",aPerson);
}
#end
The NSDictionaryResultType returns an array of dictionaries with property names and values, not an array of dictionaries with entity names and values.
Thus, not:
[
{ "person" : PersonObject },
{ "person" : OtherPersonObject }
]
but rather
[
{ "name" : "John", "age" : 30 },
{ "name" : "Jane", "age" : 20 }
]
To do what you want, you need to just fetch the CDPerson entity with NSManagedObjectResultsType.
Person *person = fetchedObjects[0];
NSLog (#"%#", person.name);
Note that "Person.name" (with a capital "P") is probably wrong, as it looks like a class method rather than an instance method.
Your fetchDistinctProperties: method needs an array of NSPropertyDescriptions but you are passing an array of NSString.