Annotation with NSManagedObject - objective-c

I'm relatively new to objective-C and iOS, so I ran into a problem using an NSManagedObject subclass as a MKAnnotation. I do a fetch request to get all 'Places' (the NSManagedObject subclass and MKAnnotation), so I can populate the map.
All fine getting fetching them on viewDidLoad on the MapController:
- (void)viewDidLoad
{
[super viewDidLoad];
DatabaseHelper *db = [DatabaseHelper newDatabaseHelper]; // does all the fetch requests ahead...
NSArray *places = [[db getPlacesForDeck:#"PlacesDeck"] allObjects];
/*
[places enumerateObjectsUsingBlock:^(Place *place, NSUInteger i, BOOL *stop) {
NSLog(#"Place Number: %d", i);
NSLog(#"Place : %#", place.name);
NSLog(#"Place latitude: %#", place.latitude); ALL FINE HERE
NSLog(#"Place longitude: %#", place.longitude);
}];
*/
self.annotations = places;
[self.mapView addAnnotations:places];
}
NSLog reports fine, however as the maps gets drawn the annotations title, and coordinates default to null, and (0,0).
- (NSString *) title{
return self.name; //name is an attribute from NSManagedObject subclass.
}
- (CLLocationCoordinate2D) coordinate{
CLLocationCoordinate2D coord;
coord.latitude = [self.latitude doubleValue]; // Longitude and latitude
coord.longitude = [self.longitude doubleValue]; // also dynamic attributes.
return coord;
}
My guess was that as I pass the 'places' array I somehow dereference each place. However I read somewhere NSArray strongly references those objects, so I'm not quite sure how am I "loosing" the data. Any tips on what's happening? Or am I taking the wrong approach?

the coordinate accessor is simpler like this:
- (CLLocationCoordinate2D)coordinate{
return CLLocationCoordinate2DMake(self.latitude, self.longitude);
}

Related

Why is NSArray of NSNumber(s) failing to decode?

I have a custom class that conforms to NSSecureCoding. I'm trying to encode its data, write it to a file, and decode it. The class contains an int array which I convert to an NSArray filled with NSNumber elements before encoding. When I decode the array, decodeObjectForKey returns nil. Also, when I write the encoded array to file, the plist doesn't look right. For example, it doesn't have the "key_for_ns_array" key anywhere in its structure.
Here's a simplified version of my code:
#interface c_my_data_wrapper : NSObject <NSSecureCoding>
#property (assign, nonatomic) struct s_my_data my_data; // the array exists as an int[] in here
#end
#implementation c_my_data_wrapper
- (void)encodeWithCoder:(NSCoder *)encoder
{
NSMutableArray *ns_array= [NSMutableArray array];
int_array_to_ns_array(self.my_data.my_int_array, count, ns_array);
[encoder encodeObject:ns_array forKey:#"key_for_ns_array"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self= [self init];
if (self)
{
// this returns nil
NSArray *ns_array= [decoder decodeObjectForKey:#"key_for_ns_array"];
}
return self;
}
#end
c_secure_storage_data_wrapper* g_my_data_wrapper= nil; // allocation not shown
void store_data(struct s_storage_data_secure *new_data)
{
g_my_data_wrapper.my_data= *new_data;
NSData *encoded_data=
[NSKeyedArchiver archivedDataWithRootObject:g_my_data_wrapper
requiringSecureCoding:YES
error:nil];
// then I write the encoded data to a file
}
void load_data(void)
{
NSData *decoded_data= [NSData dataWithContentsOfURL:my_url];
g_my_data_wrapper=
[NSKeyedUnarchiver unarchivedObjectOfClass:[c_my_data_wrapper class]
fromData:decoded_data
error:nil];
}
void int_array_to_ns_array(
int *int_array,
int count,
NSMutableArray *out_ns_array)
{
for (int index= 0; index < count; ++index)
{
[out_ns_array addObject:[NSNumber numberWithInt:int_array[index]]];
}
}
If you're curious about the whole int array to ns array conversion thing... it's because I prefer to write in c++. I'm not very experienced with objective-c, so I only write it when I need to bridge the gap between my game and iOS. In this case, s_my_data is a type that's shared between the two.
One problem, probably the problem, is that you claim your class is NSSecureCodeing-compliant, but you're still calling -decodeObjectForKey: instead of -decodeObjectOfClass:forKey::
NSArray *ns_array= [decoder decodeObjectForKey:#"key_for_ns_array"];
That's not secure. The whole point of NSSecureCoding is that you tell the decoder what type you're expecting to get back, which is what -decodeObjectOfClass:forKey: does.

string not populating with array data, even though arrays are not really empty

I am trying to create a non-Document-based application for Mac OS X that randomizes cards for the game of Dominion.
From many of the ones I have tried, the only thing I cannot seem to do is limit the number of sets picked from a selection made by the user, and things worked pretty well in my program, but I am having issues.
I am trying to get the results to print in a custom view, but every time I look at the print preview, nothing shows, except header text, as specified in an NSMutableString.
This piece of code is what is being used to print and is found in MasterViewController:
- (IBAction)print:(id)sender
{
NSMutableString *content = [[NSMutableString alloc] initWithString:#"Cards\r\n\r\n"];
for (int i = 0; i < [supply.game count]; i++)
{
[content appendFormat:#"Card: %# Set: %# Cost: %d\r\n", [supply.game[i] name], [supply.game[i] collection], [supply.game[i] cost]];
}
[content appendFormat:#"\r\n\r\nRequired\r\n\r\n"];
for (int i = 0; i < [[setup supply] count]; i++)
{
NSDictionary* current = [setup supply][i];
NSString* key = [current allKeys][0]; // get the key of the current dictionary must be 0, as there is only one key
int value = [[current valueForKey:key] integerValue]; // variable to hold key value
if (value > 0) {
[content appendFormat:#"%#: %#", key, #"Yes"];
}
else
{
[content appendFormat:#"%#: %#", key, #"No"];
}
}
printView.content = [NSMutableString stringWithString:content];
[printView print:sender];
}
the data initially gets filled into some tableviews, which displays the correct content, and the supply.game array is the exact array that contains cards used for games.
setup is a property that refers to a view controller that populates a table with kinds of cards that may be required for games (e.g. shelters, colonies, ruins, spoils, and potions) and the supply method is supposed to return the array that view controller creates, which is itself not empty, as that table populates properly.
printView is a property that is assigned to a custom view found in MainMenu.xib and is the real view being used to print from.
the printView class looks like this:
header:
#import <Cocoa/Cocoa.h>
#interface PrintView : NSView
{
NSMutableString* content;
}
#property NSMutableString* content;
- (void)drawStringInRect:(NSRect)rect; // method to draw string to page
- (void)print:(id)sender; // method to print
#end
implementation:
#import "PrintView.h"
#implementation PrintView
#synthesize content;
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (void)print:(id)sender
{
[[NSPrintOperation printOperationWithView:self] runOperation];
}
- (void)drawRect:(NSRect)dirtyRect {
NSGraphicsContext *context = [NSGraphicsContext currentContext];
if ([context isDrawingToScreen])
{
}
else
{
[[NSColor whiteColor] set];
NSRect bounds = [self bounds];
if (content == nil || [content length] == 0)
{
NSRectFill(bounds);
}
else
{
[self drawStringInRect:bounds];
}
}
}
- (void)drawStringInRect:(NSRect)rect
{
NSSize strSize; // variable to hold string size
NSPoint strOrigin; // variable used to position text
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
[attributes setObject:[NSFont fontWithName:#"Helvetica" size:12] forKey:NSFontAttributeName];
[attributes setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
strSize = [content sizeWithAttributes:attributes];
strOrigin.x = rect.origin.x + (rect.size.width - strSize.width)/2;
strOrigin.y = rect.origin.y + (rect.size.height - strSize.height)/2;
[content drawAtPoint:strOrigin withAttributes:attributes];
}
#end
When I check the array sizes for printing operation, the size of the arrays is reported as zero, thus resulting in my current problem
If you need more code, here is code from Github, but I do not have the experimental branch up there, which is where the above code came from, though it should not be too different.
The MasterViewController will show how the supply.game array is made and SetupViewController houses the code that is used to determine what is needed in the game, as well as show how the supply array from [setup supply] is being produced.
MasterViewController has also been added as an object to MainMenu.xib, so I do not know if that affects anything.
Any idea of what I need to do?
Edit: Added in info that might be relevant

NSInvalidArgumentException with NSSet in Ordered CoreData Relationship [duplicate]

On my Lion app, I have this data model:
The relationship subitems inside Item is ordered.
Xcode 4.1 (build 4B110) has created for me the file Item.h, Item.m, SubItem.h and SubItem.h.
Here is the content (autogenerated) of Item.h:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class SubItem;
#interface Item : NSManagedObject {
#private
}
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSOrderedSet *subitems;
#end
#interface Item (CoreDataGeneratedAccessors)
- (void)insertObject:(SubItem *)value inSubitemsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromSubitemsAtIndex:(NSUInteger)idx;
- (void)insertSubitems:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
- (void)removeSubitemsAtIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectInSubitemsAtIndex:(NSUInteger)idx withObject:(SubItem *)value;
- (void)replaceSubitemsAtIndexes:(NSIndexSet *)indexes withSubitems:(NSArray *)values;
- (void)addSubitemsObject:(SubItem *)value;
- (void)removeSubitemsObject:(SubItem *)value;
- (void)addSubitems:(NSOrderedSet *)values;
- (void)removeSubitems:(NSOrderedSet *)values;
#end
And here is the content (autogenerated) of Item.m:
#import "Item.h"
#import "SubItem.h"
#implementation Item
#dynamic name;
#dynamic subitems;
#end
As you can see, the class Item offers a method called addSubitemsObject:. Unfortunately, when trying to use it in this way:
Item *item = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
item.name = #"FirstItem";
SubItem *subItem = [NSEntityDescription insertNewObjectForEntityForName:#"SubItem" inManagedObjectContext:self.managedObjectContext];
[item addSubitemsObject:subItem];
this error appear:
2011-09-12 10:28:45.236 Test[2002:707] *** -[NSSet intersectsSet:]: set argument is not an NSSet
Can you help me?
Update:
After just 1,787 days from my bug report, today (August 1, 2016) Apple wrote me this: "Please verify this issue with the latest iOS 10 beta build and update your bug report at bugreport.apple.com with your results.". Let's hope this is the right time :)
I reproduced your setup both with your data model and one of my own with different names. I got the same error in both cases.
Looks like a bug in Apple's autogenerated code.
I agree that there may be a bug here. I've modified the implementation of the add object setter to append correctly to a NSMutableOrderedSet.
- (void)addSubitemsObject:(SubItem *)value {
NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
[tempSet addObject:value];
self.subitems = tempSet;
}
Reassigning the set to self.subitems will ensure that the Will/DidChangeValue notifications are sent.
I've decided to improve the solution by implementing all the required methods:
static NSString *const kItemsKey = #"<#property#>";
- (void)insertObject:(<#Type#> *)value in<#Property#>AtIndex:(NSUInteger)idx {
NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
[tmpOrderedSet insertObject:value atIndex:idx];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)removeObjectFrom<#Property#>AtIndex:(NSUInteger)idx {
NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
[tmpOrderedSet removeObjectAtIndex:idx];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)insert<#Property#>:(NSArray *)values atIndexes:(NSIndexSet *)indexes {
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
[tmpOrderedSet insertObjects:values atIndexes:indexes];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)remove<#Property#>AtIndexes:(NSIndexSet *)indexes {
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
[tmpOrderedSet removeObjectsAtIndexes:indexes];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)replaceObjectIn<#Property#>AtIndex:(NSUInteger)idx withObject:(<#Type#> *)value {
NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
[self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
[tmpOrderedSet replaceObjectAtIndex:idx withObject:value];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)replace<#Property#>AtIndexes:(NSIndexSet *)indexes with<#Property#>:(NSArray *)values {
[self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
[tmpOrderedSet replaceObjectsAtIndexes:indexes withObjects:values];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)add<#Property#>Object:(<#Type#> *)value {
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
NSUInteger idx = [tmpOrderedSet count];
NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
[tmpOrderedSet addObject:value];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}
- (void)remove<#Property#>Object:(<#Type#> *)value {
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
NSUInteger idx = [tmpOrderedSet indexOfObject:value];
if (idx != NSNotFound) {
NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx];
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
[tmpOrderedSet removeObject:value];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}
}
- (void)add<#Property#>:(NSOrderedSet *)values {
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
NSUInteger valuesCount = [values count];
NSUInteger objectsCount = [tmpOrderedSet count];
for (NSUInteger i = 0; i < valuesCount; ++i) {
[indexes addIndex:(objectsCount + i)];
}
if (valuesCount > 0) {
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
[tmpOrderedSet addObjectsFromArray:[values array]];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey];
}
}
- (void)remove<#Property#>:(NSOrderedSet *)values {
NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]];
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
for (id value in values) {
NSUInteger idx = [tmpOrderedSet indexOfObject:value];
if (idx != NSNotFound) {
[indexes addIndex:idx];
}
}
if ([indexes count] > 0) {
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
[tmpOrderedSet removeObjectsAtIndexes:indexes];
[self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey];
}
}
Yes, this is definitely a Core Data bug. I wrote up an ObjC-Runtime-based fix a while back, but at the time I figured it would be fixed soon. Anyway, no such luck, so I posted it up on GitHub as KCOrderedAccessorFix. Work around the problem on all your entities:
[managedObjectModel kc_generateOrderedSetAccessors];
One entity in particular:
[managedObjectModel kc_generateOrderedSetAccessorsForEntity:entity];
Or just for one relationship:
[managedObjectModel kc_generateOrderedSetAccessorsForRelationship:relationship];
Instead to making a copy I suggest to use the accessor in NSObject to get access to the NSMutableOrderedSet of the relationships.
- (void)addSubitemsObject:(SubItem *)value {
NSMutableOrderedSet* tempSet = [self mutableOrderedSetValueForKey:#"subitems"];
[tempSet addObject:value];
}
e.g. the Core Data Release Notes for iOS v5.0 refer to this.
In a short test it worked in my application.
I've tracked the bug. It occurs in willChangeValueForKey:withSetMutation:usingObjects:.
This call sets off a chain of notifications which may be difficult to track, and of course changes to one responder may have implications for another, which I suspect is why Apple have done nothing.
However, it is okay in Set and its only the Set operations on an OrderedSet that malfunction. That means there are only four methods that need to be altered. Therefore, all I did was convert the Set operations to their equivalent Array operations. These work perfectly and minimal (but necessary) overheads.
On a critical level, this solution does suffer from one critical flaw; if you are adding objects and one of the objects already exists, then it is either not added or moved to the back of the ordered list (I don't know which). In either case, the expected ordered index of the object by the time we arrive at didChange is different from what was anticipated. This may break some people's apps, but it doesn't affect mine, since I am only ever adding new objects or I confirm their final locations before I add them.
- (void)addChildrenObject:(BAFinancialItem *)value {
if ([self.children containsObject:value]) {
return;
}
NSIndexSet * indexSet = [NSIndexSet indexSetWithIndex:self.children.count];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
[[self primitiveValueForKey:ChildrenKey] addObject:value];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
}
- (void)removeChildrenObject:(BAFinancialItem *)value {
if (![self.children containsObject:value]) {
return;
}
NSIndexSet * indexSet = [NSIndexSet indexSetWithIndex:[self.children indexOfObject:value]];
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
[[self primitiveValueForKey:ChildrenKey] removeObject:value];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
}
- (void)addChildren:(NSOrderedSet *)values {
if ([values isSubsetOfOrderedSet:self.children]) {
return;
}
NSIndexSet * indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.children.count, values.count)];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
[[self primitiveValueForKey:ChildrenKey] unionOrderedSet:values];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:ChildrenKey];
}
- (void)removeChildren:(NSOrderedSet *)values {
if (![self.children intersectsOrderedSet:values]) {
return;
}
NSIndexSet * indexSet = [self.children indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [values containsObject:obj];
}];
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
[[self primitiveValueForKey:ChildrenKey] minusOrderedSet:values];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexSet forKey:ChildrenKey];
}
Of course, there is an easier solution. it is as follows;
- (void)addChildrenObject:(BAFinancialItem *)value {
if ([self.children containsObject:value]) {
return;
}
[self insertObject:value inChildrenAtIndex:self.children.count];
}
- (void)removeChildrenObject:(BAFinancialItem *)value {
if (![self.children containsObject:value]) {
return;
}
[self removeObjectFromChildrenAtIndex:[self.children indexOfObject:value]];
}
- (void)addChildren:(NSOrderedSet *)values {
if ([values isSubsetOfOrderedSet:self.children]) {
return;
}
[self insertChildren:values atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.children.count, values.count)]];
}
- (void)removeChildren:(NSOrderedSet *)values {
if (![self.children intersectsOrderedSet:values]) {
return;
}
[self removeChildrenAtIndexes:[self.children indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [values containsObject:obj];
}]];
}
The Apple docs To Many Relations says: you should access the proxy mutable set or ordered set using
NSMutableOrderedSet * set = [managedObject mutableOrderedSetValueForKey:#"toManyRelation"];
Modifying this set will add or remove relations to your managed object. Accessing the mutable ordered set using the accessor whether with [ ] or . notation is wrong and will fail.
Received the same error, #LeeIII solution worked for me (thanks!). I suggest slightly modify it:
use objective-c category to store the new method (so we wont lose our method if Item is generated again)
check if we already have mutable set
Content of Item+category.m:
#import "Item+category.h"
#implementation Item (category)
- (void)addSubitemsObject:(SubItem *)value {
if ([self.subitems isKindOfClass:[NSMutableOrderedSet class]]) {
[(NSMutableOrderedSet *)self.subitems addObject:value];
} else {
NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
[tempSet addObject:value];
self.subitems = tempSet;
}
}
#end
If you are using mogenerator, then instead of
[parentObject add<Child>sObject:childObject];
simply use:
[[parent object <child>sSet] addObject:childObject];
Personally I have just replaced the calls to the CoreData generated methods with direct calls to the method as outlined in another solution by #Stephan:
NSMutableOrderedSet* tempSet = [self mutableOrderedSetValueForKey:#"subitems"];
[tempSet addObject:value];
[tempSet addObject:value];
This removes the need for categories that might later conflict with a solution from Apple to the generated code when the bug is fixed.
This has the added plus of being the official way to do it!
It seems that if you link the parent with the child by setting the parent to the child and not the other way around it works without crashing.
So if you do:
[child setParent:parent]
instead of
[parent setChildObects:child]
It should work, at least it works on iOS 7 and didn't had any problems with the relationship.
I have had the same problem, but only when I tried something different to what I had been doing. I can't see the code for subItem, but I will assume that it has a reverse link to item. Lets call this reveres link, "parentItem", then the easiest solution is this:
Item *item = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
item.name = #"FirstItem";
SubItem *subItem = [NSEntityDescription insertNewObjectForEntityForName:#"SubItem" inManagedObjectContext:self.managedObjectContext];
//[item addSubitemsObject:subItem];
subItem.parentItem = item;
The effect is that it makes use of apple's own code and it is simple and clean. In addition, the set is automatically added to, and all observers are updated. No problem.
I just fell foul of this issue, and resolved it using a much simpler implementation than the others outlined here. I simply make use of the methods available on NSManagedObject for dealing with relationships when not using subclasses.
An example implementation for inserting an entity into an NSOrderedSet relationship would look like this:
- (void)addAddress:(Address *)address
{
if ([self.addresses containsObject:address]) {
return;
}
// Use NSManagedObject's methods for inserting an object
[[self mutableOrderedSetValueForKey:#"addresses"] addObject:address];
}
This works perfectly, and is what I was using before I moved to NSManagedObject subclasses.
This issue occurred to me while migrating a project from Objective-C to Swift 2 with XCode 7. That project used to work, and for a good reason: I was using MOGenerator which had replacement methods to fix this bug. But not all methods require a replacement.
So here's the complete solution with an example class, relying on default accessors as much as possible.
Let's say we have a List with ordered Items
First a quick win if you have a one/to-many relationship, the easiest is to just do:
item.list = list
instead of
list.addItemsObject(item)
Now, if that's not an option, here's what you can do:
// Extension created from your DataModel by selecting it and
// clicking on "Editor > Create NSManagedObject subclass…"
extension List {
#NSManaged var items: NSOrderedSet?
}
class List
// Those two methods work out of the box for free, relying on
// Core Data's KVC accessors, you just have to declare them
// See release note 17583057 https://developer.apple.com/library/prerelease/tvos/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc7_release_notes.html
#NSManaged func removeItemsObject(item: Item)
#NSManaged func removeItems(items: NSOrderedSet)
// The following two methods usually work too, but not for NSOrderedSet
// #NSManaged func addItemsObject(item: Item)
// #NSManaged func addItems(items: NSOrderedSet)
// So we'll replace them with theses
// A mutable computed property
var itemsSet: NSMutableOrderedSet {
willAccessValueForKey("items")
let result = mutableOrderedSetValueForKey("items")
didAccessValueForKey("items")
return result
}
func addItemsObject(value: Item) {
itemsSet.addObject(value)
}
func addItems(value: NSOrderedSet) {
itemsSet.unionOrderedSet(value)
}
end
Of course, if you're using Objective-C, you can do the exact same thing since this is where I got the idea in the first place :)
I agree that there maybe a bug here. I've modified the implementation of the add object >setter to append correctly to a NSMutableOrderedSet.
- (void)addSubitemsObject:(SubItem *)value {
NSMutableOrderedSet* tempSet = [NSMutableOrderedSet orderedSetWithOrderedSet:self.subitems];
[tempSet addObject:value];
self.subitems = tempSet;
}
Reassigning the set to self.subitems will ensure that the Will/DidChangeValue notifications >are sent.
Leelll, are you sure that after such custom setup of NSMutableOrderedSet values stored in that set will be saved to the database correctly by CoreData? I didn't check that, but it looks like CoreData knows nothing about NSOrderedSet and expects NSSet as to-many relationship container.
I think everybody is missing the real problem. It is not in the accessor methods but rather in the fact that NSOrderedSet is not a subclass of NSSet. So when -interSectsSet: is called with an ordered set as argument it fails.
NSOrderedSet* setA = [NSOrderedSet orderedSetWithObjects:#"A",#"B",#"C",nil];
NSSet* setB = [NSSet setWithObjects:#"C",#"D", nil];
[setB intersectsSet:setA];
fails with *** -[NSSet intersectsSet:]: set argument is not an NSSet
Looks like the fix is to change the implementation of the set operators so they handle the types transparently. No reason why a -intersectsSet: should work with either an ordered or unordered set.
The exception happens in the change notification. Presumably in the code that handles the inverse relationship. Since it only happens if I set an inverse relationship.
The following did the trick for me
#implementation MF_NSOrderedSetFixes
+ (void) fixSetMethods
{
NSArray* classes = [NSArray arrayWithObjects:#"NSSet", #"NSMutableSet", #"NSOrderedSet", #"NSMutableOrderedSet",nil];
[classes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* name = obj;
Class aClass = objc_lookUpClass([name UTF8String]);
[MF_NSOrderedSetFixes fixMethodWithSetArgument:#selector(intersectsSet:) forClass:aClass];
[MF_NSOrderedSetFixes fixMethodWithSetArgument:#selector(isSubsetOfSet:) forClass:aClass];
}];
}
typedef BOOL (*BoolNSetIMP)(id _s,SEL sel, NSSet*);
/*
Works for all methods of type - (BOOL) method:(NSSet*) aSet
*/
+ (void) fixMethodWithSetArgument:(SEL) aSel forClass:(Class) aClass
{
/* Check that class actually implements method first */
/* can't use get_classInstanceMethod() since it checks superclass */
unsigned int count,i;
Method method = NULL;
Method* methods = class_copyMethodList(aClass, &count);
if(methods) {
for(i=0;i<count;i++) {
if(method_getName(methods[i])==aSel) {
method = methods[i];
}
}
free(methods);
}
if(!method) {
return;
}
// Get old implementation
BoolNSetIMP originalImp = (BoolNSetIMP) method_getImplementation(method);
IMP newImp = imp_implementationWithBlock(^BOOL(NSSet *_s, NSSet *otherSet) {
if([otherSet isKindOfClass:[NSOrderedSet class]]) {
otherSet = [(NSOrderedSet*)otherSet set];
}
// Call original implementation
return originalImp(_s,aSel,otherSet);
});
method_setImplementation(method, newImp);
}
#end
I just got the problem in Swift (Xcode 6.1.1).
The answer was DO NOT CODE ANY METHOD OR ADDITIONAL THINGS in your NSManagedObject subclasses. I think it is a compilator mistake. Very strange bug ..
Hope it helps ..
I solved this problem by set the inverse to No Inverse, I don't know why, Maybe there is Apple Bug.
I have the same situation with an item called "signals" instead of "subitems". The solution with tempset works in my testing. Further, I had a problem with the removeSignals: method. This override seems to work:
- (void)removeSignals:(NSOrderedSet *)values {
NSMutableOrderedSet* tempset = [NSMutableOrderedSet orderedSetWithOrderedSet:self.signals];
for (Signal* aSignal in values) {
[tempset removeObject:aSignal];
}
self.signals = tempset;
}
If there is a better way to do this, please let me know. My values input is never more than 10 -20 items so performance isn't much of a concern - nonetheless please point out anything relevant.
Thanks,
Damien
I found this question by googling for the error message, and just wanted to point out that I ran into this error in a slightly different way (not using ordered sets). This isn't quite an answer to the given question, but I'm posting it here just in case it is helpful to anyone else who stumbles across this question while searching.
I was adding a new model version, and added some relationships to existing models, and defined the add*Object methods in the header file myself. When I tried to call them, I got the error above.
After reviewing my models, I realized I had stupidly forgotten to check the "To-Many Relationship" checkbox.
So if you're running into this and you're not using ordered sets, double check your model.
I found a fix for this bug that works for me. I just replace this:
[item addSubitemsObject:subItem];
with this:
item.subitemsObject = subItem;
Better version of the correct answer in SWIFT
var tempSet = NSMutableOrderedSet()
if parent!.subItems != nil {
tempSet = NSMutableOrderedSet(orderedSet: parent!.subItems!)
}
tempSet.add(newItem)
parent!.subItems = tempSet
I found using the method by LeeIII worked, but on profiling found it was drastically slow. It took 15 seconds to parse 1000 items. Commenting out the code to add the relationship turned 15 seconds into 2 seconds.
My workaround (which is faster but much more ugly) involves creating a temporary mutable array then copying into the ordered set when all the parsing is done. (this is only a performance win if you are going to add many relationships).
#property (nonatomic, retain) NSMutableArray* tempItems;
....
#synthesize tempItems = _tempItems;
....
- (void) addItemsObject:(KDItem *)value
{
if (!_tempItems) {
self.tempItems = [NSMutableArray arrayWithCapacity:500];
}
[_tempItems addObject:value];
}
// Call this when you have added all the relationships
- (void) commitRelationships
{
if (_tempItems) {
self.items = [NSOrderedSet orderedSetWithArray:self.tempItems];
self.tempItems = nil;
}
}
I hope this help someone else!
Robert,
I agree your answer will work for this, but keep in mind that there is an automatically created method for adding a whole set of values to a relationship already. Apple's Documentation (as seen here under the "To-many Relationships" section or here under the "Custom To-Many Relationship Accessor Methods" section) implements them this way:
- (void)addEmployees:(NSSet *)value
{
[self willChangeValueForKey:#"employees"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:value];
[[self primitiveEmployees] unionSet:value];
[self didChangeValueForKey:#"employees"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:value];
}
- (void)removeEmployees:(NSSet *)value
{
[self willChangeValueForKey:#"employees"
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:value];
[[self primitiveEmployees] minusSet:value];
[self didChangeValueForKey:#"employees"
withSetMutation:NSKeyValueMinusSetMutation
usingObjects:value];
}
You could easily compile your set of relationships outside of core data and then add them all at once using this method. It might be less ugly than the method you suggested ;)
I'm quite sure it is finally fixed in iOS 10 beta 6!

Adding NSImage to IKimagerowserview

I am able to add NSImages to my NSCollectionView without having to first save them on disk. The images are fed into the collection view from an NSMutableArray. This way people can see the images without first having to save them.
Is there something similar that I can achieve with IKImageBrowserView? NSCollectionView is functional when it comes to representing images, but I would like to see if I can do something similar with IKImageBrowserView.
I can easily implement IKImageBrowserView with images saved on disk (Apple docs cover how this works) but can't figure out exactly where to look or how to go about adding images to the browser view directly from NSMutableArray instead of first saving them images to disk.
I'm at a loss here. Apart from the docs, I'm not really sure where else to look for direction. Or what to even call what I'm looking to do.
EDIT: (Here's some of the code)
// The data object -- if it is possible to represent an image object, this is where I am probably going wrong.
#interface ImageObject : NSObject
#property (readwrite, copy) NSImage *image;
#property (readwrite, copy) NSString *imageID;
- (id)initWithImage:(NSImage *)anImage;
- (NSString *)imageUID;
- (NSString *)imageRepresentationType;
- (id)imageRepresentation;
#end
#implementation ImageObject
#synthesize image = _image;
#synthesize imageID = _imageID;
- (id)initWithImage:(NSImage *)anImage
{
if (self = [super init]) {
_image = [anImage copy];
}
return self;
}
- (NSString *)imageUID
{
return _imageID;
}
- (NSString *)imageRepresentationType
{
return IKImageBrowserNSImageRepresentationType;
}
- (id)imageRepresentation
{
return _image;
}
#end
// This is how objects are supposed to be added to the browserView. All of this is straight from Apple.
- (void)updateDatasource
{
[_browserImages addObjectsFromArray:_importedImages];
[_importedImages removeAllObjects];
[imageBrowser reloadData];
}
- (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser
{
return [_browserImages count];
}
- (id)imageBrowser:(IKImageBrowserView *)aBrowser itemAtIndex:(NSUInteger)index
{
return [_browserImages objectAtIndex:index];
}
This is where I try to add NSImages to the browserView but nothing happens. The array gets populated (which means the images are generated without any errors) but nothing happens on the screen.
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[oPanel URL] options:nil];
NSMutableArray *timesArray = [self generateTimeForSpecifiedNumberOfFramesInVideo:10 UsingAsset:asset];
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
[[self imageGenerator] generateCGImagesAsynchronouslyForTimes:timesArray completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
NSImage *testImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
if (result == AVAssetImageGeneratorSucceeded) {
ImageObject *objects = [[ImageObject alloc] initWithImage:testImage];
[_importedImages addObject:objects];
}
}
As for exploring the rest of the search results...been there done that. If I did miss anything, kindly mark this question as duplicate indicating what post already existed where this issue has been addressed.
EDIT:
I have accepted the answer below. Along with the unique IDs problem. I had overlooked a simple thing which was the requirement to call the updateDatasource method.
The most important point of using IKImageBrowser is create a unique image ID for each element. The following is an example. In fact, it comes from the project that I'm currently working on. I have just implemented IKImageBrowser in it. The code below assumes that you have 36 images (Image01.png, Image02.png..., Image36.png) imported into the project.
// .h
#interface AppDelegate : NSObject {
IBOutlet IKImageBrowserView *browserView;
NSMutableArray *imageArray;
}
// .m
#import "IKBBrowserItem.h"
#import <QuartzCore/QuartzCore.h>
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
imageArray = [[NSMutableArray alloc]init];
}
- (void)populateImage {
for (NSInteger i2 = 1; i2 <= 36 ; i2++) {
NSString *name;
if (i2 < 10) {
name = [NSString stringWithFormat:#"Image0%ld",(long)i2];
} else {
name = [NSString stringWithFormat:#"Image%ld",(long)i2];
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
NSImage *Image0 = [NSImage imageNamed:name];
NSInteger ran = [self genRandom:1000000:9999999];
NSString *imageID = [NSString stringWithFormat:#"%#%li",name,ran];
IKBBrowserItem *item = [[IKBBrowserItem alloc] initWithImage:Image0 imageID:imageID:name];
[imageArray addObject:item];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
[browserView reloadData];
}
- (NSInteger)genRandom: (NSInteger)min :(NSInteger)max {
int num1;
do {
num1 = arc4random() % max;
} while (num1 < min);
return num1;
}
You don't need to use a random integer generator (genRandom) above, but just make sure that no imageID is the same.
This web site has a sample project, which should get you going. (I have no affiliation.) So make sure you download and run it. Then take a closer look and improve it for your needs.

Create a NSSet from NSArray based on property

How does one create a NSSet of objects from an array based on a property.
e.g. Array of objects, each with a strong reference to a type property, and multiple occurrences of each type exist in the array. How can this be turned into an NSSet holding a single object of each type.
NSSet *distinctSet = [NSSet setWithArray:[array valueForKeyPath:#"#distinctUnionOfObjects.property"]];
A dictionary essentially has this functionality already. Its keys are a set, so you can create the dictionary to hold the objects, keyed by whatever attribute you're interested in:
[NSDictionary dictionaryWithObjects:arrayOfObjects
forKeys:[arrayOfObjects valueForKey:theAttribute]];
If you ask the dictionary for allValues now, you have only one object for each attribute. I should mention that with this procedure, the later objects will be kept in favor of earlier. If the order of your original array is significant, reverse it before creating the dictionary.
You can't actually put those objects into an NSSet, because the NSSet will use the objects' isEqual: and hash methods to determine whether they should be members, rather than the key attribute (of course, you can override these methods if this is your own class, but that would likely interfere with their behavior in other collections).
If you really really feel that you need a set, you will have to write your own class. You can subclass NSSet, but conventional wisdom is that composition of Cocoa collections is far easier than subclassing. Essentially, you write a class which covers any set methods you're interested in. Here's a (quite incomplete and totally untested) sketch:
#interface KeyedMutableSet : NSObject
/* This selector is performed on any object which is added to the set.
* If the result already exists, then the object is not added.
*/
#property (assign, nonatomic) SEL keySEL;
- (id)initWithKeySEL:(SEL)keySEL;
- (id)initWithArray:(NSArray *)initArray usingKeySEL:(SEL)keySEL;
- (void)addObject:(id)obj;
- (NSArray *)allObjects;
- (NSArray *)allKeys;
- (BOOL)containsObject:(id)obj;
- (NSUInteger)count;
-(void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block;
// And so on...
#end
#import "KeyedMutableSet.h"
#implementation KeyedMutableSet
{
NSMutableArray * _objects;
NSMutableSet * _keys;
}
- (id)initWithKeySEL:(SEL)keySEL
{
return [self initWithArray:nil usingKeySEL:keySEL];
}
- (id)initWithArray:(NSArray *)initArray usingKeySEL:(SEL)keySEL
{
self = [super init];
if( !self ) return nil;
_keySEL = keySEL;
_objects = [NSMutableArray array];
_keys = [NSMutableSet set];
for( id obj in initArray ){
[self addObject:obj];
}
return self;
}
- (void)addObject:(id)obj
{
id objKey = [obj performSelector:[self keySEL]];
if( ![keys containsObject:objKey] ){
[_keys addObject:objKey];
[_objects addObject:obj];
}
}
- (NSArray *)allObjects
{
return _objects;
}
- (NSArray *)allKeys
{
return [_keys allObjects];
}
- (BOOL)containsObject:(id)obj
{
return [_keys containsObject:[obj performSelector:[self keySEL]]];
}
- (NSUInteger)count
{
return [_objects count];
}
- (NSString *)description
{
return [_objects description];
}
-(void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block
{
for( id obj in _objects ){
BOOL stop = NO;
block(obj, &stop);
if( stop ) break;
}
}
#end
NSMutableSet* classes = [[NSMutableSet alloc] init];
NSMutableSet* actualSet = [[NSMutableSet alloc] init];
for(id object in array) {
if([classes containsObject:[object class]] == NO) {
[classes addObject:[object class]];
[actualSet addObject:object];
}
}
You would use:
NSSet* mySetWithUniqueItems= [NSSet setWithArray: yourArray];
This should work regardless of the type of objects in your array and would populate the NSSet with only one occurence of any duplicate objects in your array.
I hope this helps.
Update:
Next best thing: is use concatenation of class name and object property first then use the above method.
self.concatenatedArray=[NSMutableArray arrayWithCapacity:4];
for (TheClass* object in self.myArray)
[self.concatenatedArray addObject:[NSString stringWithFormat:#"%#-%#",[object class], object.theProperty]];
self.mySet=[NSSet setWithArray:self.concatenatedArray];
I am not sure what you will use the NSSet output for but you can probably modify the concatenation elements to have the information you need in the NSSet output.
I have created a simple library, called Linq to ObjectiveC, which is a collection of methods that makes this kind of problem much easier to solve. In your case you need the Linq-to-ObjectiveC distinct method:
NSSet* dictionary = [NSSet setWithArray:[sourceArray distinct:^id(id item) {
return [item type] ;
}]];
This returns a set where each item has a distinct type property.