I'm doing my head in trying to figure out an issue I'm having with core-data. I understand what the error means, and I've had it before (and fixed it) but I can't figure out why I'm getting it now.
My app has the following structure :
MPModel -> MPPlace and MPModel -> MPProject
Where MPModel is a subclass of NSManagedObject and MPPlace and MPProject are subclasses of MPModel.
The data model has a relationship between MPPlace and MPProject where MPPlace has-many MPProjects and MPProject belongs-to MPPlace.
When the app loads, it fetches a number of MPPlace objects which works perfectly. When a user selected a place and then selects the projects option, the app attempts to retrieve the list of projects. This is where the app fails however, with the following error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Failed to process pending changes before save. The context is still dirty after 100
attempts. Typically this recursive dirtying is caused by a bad validation method,
willSave, or notification handler.'
Each MPModel contains a number of custom methods, including saveAllLocally which simply saves an array of objects to the persistence store if they don't already exist, and loadNestedResources which fetches new objects from the server that are related to the current object. I am finding that the failure is occurring with the loadNestedResources method but I can't figure out why.
The loadNestedResources method looks like this :
- (void) loadNestedResourcesOfType:(Class)type atURL:(NSString *)resURL withBlock:(MPFindObjectsBlock)block
{
if (![type isSubclassOfClass:[MPModel class]]) {
block(nil, nil);
} else {
NSString *url = [NSString stringWithFormat:#"%#%#/%#%#", kMP_BASE_API_URL, [self.class baseURL], self.objID, [type baseURL]];
MPRequest *request = [[MPRequest alloc] initWithURL:url];
// Attempt to see if we already have this relation
NSString *relation = [[MPUtils stripClassName:type].lowercaseString stringByAppendingString:#"s"];
NSSet *relatedObjects = [self valueForKey:relation];
if (relatedObjects && relatedObjects.count > 0) {
// We have some objects so lets exclude these from our request
NSMutableArray *uuids = [NSMutableArray arrayWithCapacity:0];
for (MPModel *obj in relatedObjects) {
[uuids addObject:obj.uuid];
}
[request setParam:[uuids componentsJoinedByString:#";"] forKey:#"exclude_uuids"];
}
[MPUser signRequest:request];
[request setRequestMethod:#"POST"];
[request submit:^(MPResponse *resp, NSError *error) {
if (error) {
if (relatedObjects.count > 0) {
block([relatedObjects allObjects], nil);
} else {
block(nil, error);
}
} else {
// Combine all of our objects
NSMutableArray *allObjects = [[type createListWithResponse:resp] mutableCopy];
if (allObjects.count > 0) {
[allObjects addObjectsFromArray:[relatedObjects allObjects]];
// Make sure they're now all saved in the persistence store
NSArray *savedObjects = [MPModel saveAllLocally:allObjects forEntityName:NSStringFromClass(type)];
for (NSObject *obj in savedObjects) {
[obj setValue:self forKey:[MPUtils stripClassName:self.class].lowercaseString];
}
// Set them as our related objects for this relationship
[self setValue:[NSSet setWithArray:savedObjects] forKey:relation];
[MPModel saveAllLocally:savedObjects forEntityName:NSStringFromClass(type)];
block(allObjects, nil);
} else {
block([relatedObjects allObjects], nil);
}
}
}];
}
}
The methods runs perfectly right up until the second call to saveAllLocally which is where I get the error. The MPModel class also uses the willSave method, which has the following :
- (void) willSave
{
NSDate *now = [NSDate date];
if (!self.updated_at) {
self.updated_at = now;
}
if (!self.created_at) {
self.created_at = now;
}
if (!self.uuid) {
self.uuid = [self createUUID];
}
if (!self.last_sync) {
self.last_sync = now;
}
if ([self isUpdated] && self.changedValues.count > 0) {
if (!self.attribute_updated_at) {
NSDictionary *attributes = [self.entity attributesByName];
NSMutableDictionary *dates = [NSMutableDictionary dictionaryWithCapacity:0];
for (NSString *attr in attributes.allKeys) {
[dates setObject:now forKey:attr];
}
[self setAttributeUpdatedDates:dates];
}
if (_updatedAtSet) {
_updatedAtSet = NO;
} else {
if ([self.changedValues.allKeys containsObject:#"last_sync"]) {
self.updated_at = [self.changedValues objectForKey:#"last_sync"];
} else {
self.updated_at = [NSDate date];
}
_updatedAtSet = YES;
NSDictionary *changed = [self changedValues];
NSMutableDictionary *dates = [[self attributeUpdatedDates] mutableCopy];
for (NSString *key in changed.allKeys) {
[dates setObject:now forKey:key];
}
[self setAttributeUpdatedDates:dates];
}
}
}
From what I can gather, this should be fine as it shouldn't be setting any more values if the _updatedAtSet variable is set to true, however it is still breaking and I cannot figure out why!
Please can someone help me
Thanks
Have solved it.
I just moved the _updatedAtSet = NO; into the didSave method rather than where it is and it's working fine now. Thanks anyway
Related
Here's what I'm doing:
- (void)doCreateGroup {
[[self contentView] endEditing:true];
NSString * newString = [[[[self contentView] groupNameField] text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString * firstError = nil;
if ([newString length] == 0) {
firstError = #"Missing group name";
}
NSError * groupsError = nil;
NSArray * groups = [self.contactStore groupsMatchingPredicate:nil error:&groupsError];
for (CNGroup * group in groups) {
if ([group.name isEqualToString:newString]) {
firstError = #"Group already exists";
}
}
if (firstError) {
[self presentViewController:[WLGCommonUtilities doProcessErrorWithOkay:#"Error" errorMessage:firstError] animated:YES completion:nil];
return;
}
CNMutableGroup * newGroup = [CNMutableGroup new];
[newGroup setName:newString];
CNSaveRequest *saveRequest = [CNSaveRequest new];
[saveRequest addGroup:newGroup toContainerWithIdentifier:nil];
NSError * error = nil;
[self.contactStore executeSaveRequest:saveRequest error:&error];
if (error) {
[self presentViewController:[WLGCommonUtilities doProcessErrorWithOkay:#"Error" errorMessage:[error localizedDescription]] animated:YES completion:nil];
} else {
CNSaveRequest *saveRequest2 = [CNSaveRequest new];
NSArray * groupsAgain = [self.contactStore groupsMatchingPredicate:nil error:&groupsError];
CNGroup * gotGroup;
for (CNGroup * group in groupsAgain) {
if ([group.name isEqualToString:newString]) {
gotGroup = group;
}
}
for (CNContact * contact in self.selectedContactsArray) {
[saveRequest2 addMember:contact toGroup:gotGroup];
}
NSError * error1 = nil;
[self.contactStore executeSaveRequest:saveRequest2 error:&error1];
if (error) {
[self presentViewController:[WLGCommonUtilities doProcessErrorWithOkay:#"Error" errorMessage:[error1 localizedDescription]] animated:YES completion:nil];
} else {
[[self navigationController] dismissViewControllerAnimated:true completion:nil];
}
}
}
this works to create a CNGroup and then add contacts to said CNGroup. Works for all contacts EXCEPT for unified contacts. I've tried everything possible to make this work and it just doesn't. It likely has something to do with the unified CNContact's identifier since that identifier is only stored in temp memory so it can't be added to a CNGroup since it doesn't really haver a REAL CNContact identifier. Contacts framework is a mess! Any help would be appreciated. I've also filed a tech support request with Apple.
EDIT:
One way to get around this is to use Address Framework that is now deprecated. I can add as many unified contacts to Address groups by doing this.
ABRecordRef group = ABGroupCreate();
ABAddressBookAddRecord(addressBook, group, nil);
ABRecordSetValue(group, kABGroupNameProperty,#"My Groups", nil);
for (int i=0;i < nPeople;i++) {
ABRecordRef ref = CFArrayGetValueAtIndex(allPeople,i);
ABGroupAddMember(group, ref, nil);
ABAddressBookSave(addressBook, nil);
}
this does save everything in the contact book to a group, all visible contacts that is. so it does store the Unified contact into the group. if you unlink the contacts while they are in a group, both contacts stay within the group. so the old framework works to solve this. just seems ridiculous that it can't be solved with new Contacts framework. Again, I may be missing something with the new Contacts framework, so if this is possible with the current Contacts framework in iOS please let me know.
i figured it out. this is a mess
step one:
NSMutableArray * finalArray = [NSMutableArray array];
NSMutableArray * unifiedContacts = [NSMutableArray array];
NSMutableArray * fullContacts = [NSMutableArray array];
CNContactFetchRequest * request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
[request setSortOrder:CNContactSortOrderGivenName];
[self.contactStore enumerateContactsWithFetchRequest:request error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
[unifiedContacts addObject:contact];
}];
CNContactFetchRequest * request2 = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
[request2 setUnifyResults:false];
[request2 setSortOrder:CNContactSortOrderGivenName];
[self.contactStore enumerateContactsWithFetchRequest:request2 error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
[fullContacts addObject:contact];
}];
for (CNContact * contctUn in unifiedContacts) {
NSMutableArray * nestedContacts = [NSMutableArray array];
for (CNContact * contct in fullContacts) {
if ([contctUn isUnifiedWithContactWithIdentifier:contct.identifier]) {
[nestedContacts addObject:contct];
}
}
if (nestedContacts.count) {
[finalArray addObject:#{#"contact" : contctUn, #"linked" : nestedContacts}];
} else {
[finalArray addObject:#{#"contact" : contctUn}];
}
}
self.mainArray = [finalArray mutableCopy];
this pulls in all contacts from unified contacts and then pulls in all un-unified contacts, splices the groups together and saves them as dictionaries with "linked" being an array of linked contacts if the contact is indeed linked to the contact in question.
step 2: create a group ... this is pretty simple, no need to show the code since this is pretty easy
step 3:
for (id obj in self.filteredSearchArray) {
if ([obj valueForKey:#"linked"]) {
for (id obj2 in [obj valueForKey:#"linked"]) {
[self.selectedContactsArray addObject:obj2];
}
}
}
CNSaveRequest *saveRequest2 = [CNSaveRequest new];
for (CNContact * contact in self.selectedContactsArray) {
[saveRequest2 addMember:contact toGroup:[newGroup copy]];
}
NSError * error1 = nil;
[self.contactStore executeSaveRequest:saveRequest2 error:&error1];
self.selectedContactsArray is the array that contains the contacts you want in the group. it contains all contacts you want in the group in addition it contains the sublinked contacts if a contact you want in the group is linked to a user.
when this save request executes the group now contains the unified contact.
this is a mess. Contacts Framework in iOS is a mess, but this works. No app that creates groups for contacts has solve this, so here's the million dollar solution.
That seems odd indeed. As at least a workaround, have you tried to fetch the selected contacts with a CNContactFetchRequest that has its unifyResults set to false?
I mean, I don't know where you get the selectedContactsArray from, I assume either you can modify an existing request that gave you that data accordingly or you have to somehow refetch the contacts again. That's probably really, really ugly, as you would have to construct a fetch request with a predicate or key set that is guaranteed to match the same contacts (and only those contacts) plus said unifyResults member set to false.
I'd imagine something like this (sorry for using swift, it's a little compacter for me right now, I hope that's okay):
let allMyIds: [String] = self.selectedContactsArray.map { $0.identifier }
let predicate: NSPredicate = CNContact.predicateForContacts(withIdentifiers: allMyIds)
let fetchRequest = CNContactFetchRequest(keysToFetch: someKeys)
// not sure what you'd need here for someKeys...
// I assume it would have to be a key definitely present in all contacts you
// are interested in, e.g. name? I might be wrong though...
fetchRequest.unifyResults = false
_ = self.contactStore.enumerateContacts(with: fetchRequest, usingBlock: { contact, errorPointer in
// your group adding/save preparation code here
})
I admit I am not that familiar with the Contacts framework, so I can't say whether that is really feasible. Especially the set of keys you'd have to provide to the enumerate... method might be tricky if you don't have a key that's guaranteed to be part of all contacts you want.
I apologize for such a half-baked answer, but maybe it can at least give you a new impulse.
I have a Chat-App with a Data-Modell like this.
User <--> Conversation <-->> Message
My Problem now: Sometimes, if I get old messages from a Backup, I have Messages twice in my DataModel. I'd like to have a NSSet-Like Class which recognizes, if a Message-Object has exactly the same values on it's properties. I've read, that I must not override the methods -hash and -isEqual:, so I don't know how to do it. Any Idea? Here is some code...
+(void)addMessages:(NSSet<JSQMessage *> *)messages toConversation:(Conversation *)conversation
{
DataManager * dataManager = [DataManager dataManager];
NSMutableSet * storeSet = [NSMutableSet setWithCapacity:messages.count];
for (JSQMessage * jsqMessage in messages) {
Message * message = [NSEntityDescription insertNewObjectForEntityForName:CDEntityNameMessage inManagedObjectContext:[dataManager managedObjectContext]];
message.senderId = jsqMessage.senderId;
message.senderDisplayName = jsqMessage.senderDisplayName;
message.text = jsqMessage.text;
message.date = jsqMessage.date;
[storeSet addObject:message];
}
[conversation addMessages:storeSet];
NSError *error;
if (![[dataManager managedObjectContext] save:&error]) {
NSLog(#"Something went wrong: %#", [error localizedDescription]);
} else {
//Saved successfull
}
}
And the Conversation -addMessages: Method is the one automatically generated from Xcode/CoreData
- (void)addMessages:(NSSet<Message *> *)values;
One way of doing it would be to add unique constraints on your entity for one or more attributes. But, this feature was added from iOS 9. Here's the link to the WWDC video explaining it:
https://developer.apple.com/videos/play/wwdc2015/220/
As a final option, you can always override hash and equal, if that suits your logic and requirements.
You hash method could look something like this:
- (NSUInteger)hash
{
NSInteger hashResult = 0;
for (NSObject *ob in self)
{
hashResult ^= [ob hash];
}
}
This is not the best implementation of a hash function. Check out this answer: https://stackoverflow.com/a/5915445/2696922
For the isEqual method, it could look something like:
- (BOOL)isEqual:(id)object
{
if (self == object)
{
return YES;
}
if (object == nil || ![object isKindOfClass:[JSQMessage class]])
{
return NO;
}
JSQMessage *jsqMessage = (JSQMessage*)object;
//You can have more parameters here based on your business logic
if (self.message != jsqMessage.message && self.date != jsqMessage.date)
{
return NO;
}
}
What I do now is checking manually, if there is a Object with same Attributes in my MOC. If there is one, I skip the creation. I know, it is a bit inefficient but with my expected number of messages, this should be no problem.
NSFetchRequest * fr = [NSFetchRequest fetchRequestWithEntityName:CDEntityNameMessage];
[fr setPredicate:[NSPredicate predicateWithFormat:#"text == %# AND date == %# AND conversation.user.objectId == %#", message.text, message.date, chatpartner.objectId]];
NSArray * results = [[self managedObjectContext] executeFetchRequest:fr error:nil];
if (results && results.count > 0) {
continue;
}
TTNetworkDataSource* netSource = [TTNetworkDataSource sharedSource];
[netSource setContext:self];
TTDataSourceResponse* districtsResponse = [netSource requestDataWithQuery:#{#(OBJECT_TYPE): [HotlineDistrict clsName]}];
if (districtsResponse != nil) {
NSArray* objects = [self executeFetchRequestUnsafe:[NSFetchRequest fetchRequestWithEntityName:[HotlineDistrict clsName]] error:&localError];
NSInteger i = 0;
if (localError != nil) {
result = NO; return;
} else {
NSLog(#"%lu = fetchedObjects, %lu = districtsResponse", (unsigned long)objects.count, (unsigned long)districtsResponse.objects.count);
for (NSManagedObject* obj in objects) {
NSLog(#"Deleted Objects in fetched objects %ld", (unsigned long)i++);
[self deleteObject:obj];
}
for (NSManagedObject* obj in districtsResponse.objects) {
NSLog(#"Inserted Object in districtsResponse %ld", (unsigned long)i++);
[self insertObject:obj];
}
}
} else {
localError = [NSError errorInfo:#{NSLocalizedDescriptionKey: #"Error when getting districts."}];
result = NO;
}
TTDataSourceResponse* requestTypeResponse = [netSource requestDataWithQuery:#{#(OBJECT_TYPE): [HotlineRequestType clsName]}];
if (requestTypeResponse != nil) {
NSArray* objects = [self executeFetchRequestUnsafe:[NSFetchRequest fetchRequestWithEntityName:[HotlineRequestType clsName]] error:&localError];
NSInteger j = 0;
if (localError != nil) {
result = NO; return;
} else {
NSLog(#"%lu = fetchedObjects, %lu = requestTypeResponse", (unsigned long)objects.count, (unsigned long)requestTypeResponse.objects.count);
for (NSManagedObject* obj in objects) {
NSLog(#"Deleted Objects in fetched objects %ld", (unsigned long)j++);
[self deleteObject:obj];
}
for (NSManagedObject* obj in requestTypeResponse.objects) {
NSLog(#"Deleted Objects in requestTypeResponse %ld", (unsigned long)j++);
[self insertObject:obj];
}
}
} else {
localError = [NSError errorInfo:#{NSLocalizedDescriptionKey: #"Error when getting request types."}];
result = NO;
}
Here's my code, after [self deleteObject:obj] my app hangs
Manual debug by:
NSLog(#"%lu = fetchedObjects, %lu = districtsResponse", (unsigned long)objects.count, (unsigned long)districtsResponse.objects.count);
Give this result:
802 = fetchedObjects, 47 = districtsResponse
NSLogs in loops give enumeration of objects, in total we have summ of fetchedObjects and districtsResponse, even if place NSLog in the end of loop body. So its says that loop finished well. But the app is hangs. Those loops 2, it don't touch second loop, and if we delete those loops with deleteObject - it works.
You must not subclass NSManagedObjectContext as Apple says:
https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/index.html
Subclassing Notes
You are strongly discouraged from subclassing NSManagedObjectContext. The change tracking and undo management mechanisms are highly optimized and hence intricate and delicate. Interposing your own additional logic that might impact processPendingChanges can have unforeseen consequences. In situations such as store migration, Core Data will create instances of NSManagedObjectContext for its own use. Under these circumstances, you cannot rely on any features of your custom subclass. Any NSManagedObject subclass must always be fully compatible with NSManagedObjectContext (that is, it cannot rely on features of a subclass of NSManagedObjectContext).
I found the answer, our api sended nil values to the required attribute in entity, but anyway, thanks a lot guys
I'm using MagicalRecord to change my custom CoreData functions with something better.
I have two entities: Offer (videogame offers) and System (consoles, PC, etx). An Offer can have one or many Systems, and I get all the data from a Parse query, where I save all my data.
I only have 8 Systems so when my app starts, I fetch all of them and save with Magical Record. Then I call my method to fetch some offers and transform the PFObjects from Parse into entities.
This is how
+ (void)createOrUpdateOffersWithArray:(NSArray *)objects completion:(void (^)())completion
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
for (PFObject *object in objects) {
Offer *offer = [Offer MR_findFirstByAttribute:#"offerId" withValue:object.objectId inContext:localContext];
if (offer == nil) {
offer = [Offer MR_createEntityInContext:localContext];
}
// Here I set all the offer values (title, date, etc)...
NSSet *offerSavedSystems = [offer mutableSetValueForKey:#"systems"];
if (offerSavedSystems == nil) {
offerSavedSystems = [[NSMutableSet alloc] init];
}
/* This is a set of all the systems related with the offer */
NSArray *offerSystems = [object objectForKey:#"sistemas"];
NSMutableSet *updatedSystems = [[NSMutableSet alloc] init];
/* Here I query one of my System entities for each in the "offerSystems" array */
for (NSString *systemKey in offerSystems) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
[updatedSystems addObject:system];
}
offer.systems = updatedSystems;
}
} completion:^(BOOL contextDidSave, NSError *error) {
/* UI update*/
}];
}
The weird this happens inside the last for loop. Despite I'm sure that all the 8 systems are inside my CoreData model, this line
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
returns nil
But the most weird thing is that using NSLOG just before the for loop like this
NSLog(#"SYSTEMS %d", [System MR_countOfEntities]);
NSLog(#"SYSTEMS WITH LOCAL CONTEXT %d", [System MR_countOfEntitiesWithContext:localContext]);
I got this
SYSTEMS 8
SYSTEMS WITH LOCAL CONTEXT 0
My Systems are previously saved with this method
+ (void)initializeSystems
{
NSArray *systemsKeys = [[self systems] allKeys];
for (NSString *systemKey in systemsKeys) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey];
if (system == nil) {
system = [System MR_createEntity];
}
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
system.key = systemKey;
system.name = [self literalForSystem:systemKey];
system.subscribed = [NSNumber numberWithBool:NO];
}];
}
}
What I'm doing wrong?
Thanks for your time
There are several possible problems with this line of code
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
systemKey value does not exist inside all of your entities.
system.key value not setted (or nil)
So, check firstly - fetch all system entities and log 'key' value. Insure that your key really exist.
Secondly,it's better to refactor your code for background saving
+ (void)initializeSystemsWithCompletion:(void (^)())completion
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *systemsKeys = [[self systems] allKeys];
for (NSString *systemKey in systemsKeys) {
System *system = [System MR_findFirstByAttribute:#"key" withValue:systemKey inContext:localContext];
if (system == nil) {
system = [System MR_createEntityInContext:localContext];
}
system.key = systemKey;
system.name = [self literalForSystem:systemKey];
system.subscribed = [NSNumber numberWithBool:NO];
}
} completion:^(BOOL success, NSError *error) {
}];
}
After I updated the Core data with the right value, i would like to add these value to the map view.
This the code that a I'd wrote for doing this it's this:
-(void)updateMapWithPredicate:(NSPredicate *)predicate
{
int numberOfElementiOnMap = 0;
dispatch_async(dispatch_get_main_queue(), ^{
[self.ActiviIndicator startAnimating];
[self.mapView removeAnnotations:self.mapView.annotations];
});
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
if (predicate) {
request.predicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"(latitude != nil) AND (longitude != nil)"]
,predicate]];
} else {
request.predicate = [NSPredicate predicateWithFormat:#"(latitude != nil) AND (longitude != nil)"];
}
//Get all different coordinate from core data.
[request setPropertiesToFetch:#[#"latitude", #"longitude"]];
[request setReturnsDistinctResults:YES];
[request setResultType:NSDictionaryResultType];
// Execute the fetch
NSError *error = nil;
NSArray *photos = [self.context executeFetchRequest:request error:&error];
for (NSDictionary *coordinateDic in photos) {
NSPredicate *predicateToSend;
if (predicate) {
/* Get all pictures that corresponding to the coordinate inside the coordinateDic
*/
predicateToSend = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"(latitude == %#) AND (longitude == %#)",
[coordinateDic valueForKey:#"latitude"], [coordinateDic valueForKey:#"longitude"]],predicate]];
} else {
predicateToSend = [NSPredicate predicateWithFormat:#"(latitude == %#) AND (longitude == %#)",[coordinateDic valueForKey:#"latitude"], [coordinateDic valueForKey:#"longitude"]];
}
dispatch_async(dispatch_get_main_queue(), ^{
//get all picture with the same lautide and longitude that are specified on predicate
NSArray *photos2 = [Photo pictureFromContext:self.context withPredicate:predicateToSend];
//if the pictures are more then one, we add only one picture, and we write the number of pictures as a subtitle of the mkannotationview
if ([photos2 count] > 1) {
Photo *photo = [photos2 lastObject];
photo.photoDescription = [NSString stringWithFormat:#"album with %lu picures",(unsigned long)[photos2 count]];
if (photo) {
// [self.mapView addAnnotation:photo];
}
} else { //add the only pictures
if ([photos2 firstObject]) {
[self.mapView addAnnotation:[photos2 firstObject]];
}
}
});
}
}
On this code i basically retrieve the value that in need from core date, and then add it to the map.
The objects that I'm adding to the map are obviously conform to the <MKAnnotation> protocol. I got the error on the [self.mapView addAnnotation:[photos2 firstObject]];line. The strange fact is that some object that i get from core date are correctly added to the map. I tried to debug the code by enabling the NSZombie but i got the same error. I also check the coordinate and everything it's all right. The only difference that i found while I'm debugging the code it's this:
The object that are added correctly to the maps looks like this in the debugger:
Instead when i get the error, the object that I'm adding to the map looks like this on the simulator:
and when i print the content of: 0 i get this:
Printing description of *([0]->[0]):
(Photo_Photo_) [0] = {}
which it's look like a pointer to an empty entity.
do you have any idea of how can i solve this?
Thanks
After submitting the bug report Apple told me the solution for my problem. The problem was that i was updating the coordinate in a non KVO compliant way. I store the properties like you, with latitude and longitude. Try adding this on your Photo class.
- (void)willChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"latitude"] || [key isEqualToString:#"longitude"]) { [self willChangeValueForKey:#"coordinate"]; }
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"latitude"] || [key isEqualToString:#"longitude"]) { [self didChangeValueForKey:#"coordinate"]; }
[super didChangeValueForKey:key];
}
Hope this helps.