How to add a binding programmatically for a NSTabView? - objective-c

My application contains an NSTabView with two tabs. Further, the application itself has a playState which is an enum. The playState is kept in a Singleton.
typedef enum {
kMyAppPlayStatePlaying,
kMyAppPlayStatePaused
} MyAppPlayState;
The playState gets synthesized here.
#property (readwrite) MyAppPlayState playState;
I want to switch the NSTabView every time the playState changes. Therefore, I prepared an IBOutlet to add a binding similar to this one.
[self.playPauseTabView bind:#"selectedItemIdentifier" toObject:[MyAppState sharedState] withKeyPath:#"playState" options:nil];
I already recognized the identifier must be NSString. This does not match with my enum which is an int. I could maybe use an NSValueTransformer to fix this.
Further, selectedItemIdentifier does not exists. NSTabView only offers selectedTabViewItem which then allows to access identifier or label. Though, I cannot find a way to switch the item itself based on the identifer.

In situations like that, I find myself doing one of two things:
1) Register self (or some other object) as an observer of the property in question, and set the selected tab accordingly in -observeValueForKeyPath:ofObject:change:context:. It could look like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == PlayStateChange )
{
if ( [[change objectForKey: NSKeyValueChangeKindKey] integerValue] == NSKeyValueChangeSetting )
{
NSNumber *oldValue = [change objectForKey: NSKeyValueChangeOldKey];
NSNumber *newValue = [change objectForKey: NSKeyValueChangeNewKey];
NSInteger oldInteger = [oldValue integerValue];
NSInteger newInteger = [newValue integerValue];
NSLog(#"Old play state: %ld, new play state: %ld", (long)oldInteger, (long)newInteger);
// Do something useful with the integers here
}
return;
}
}
2) declare a readonly NSString * property and declare that its value is affected by your playState property. Something like this:
#property (readonly) NSString *playStateStr;
// Accessor
-(NSString *)playStateStr
{
return playState == kMyAppPlayStatePlaying ? #"playing" : "paused";
}
+(NSSet *)keyPathsForValuesAffectingPlayStateStr
{
return [NSSet setWithObject: #"playState"];
}
Now you have an NSString-typed property that you can bind your tab view's selection.

I forgot to connect the NSTabView with its IBOutlet in the Interface Builder.
The following works for me.
NSDictionary* playStateOptions = [NSDictionary dictionaryWithObject:[[PlayStateValueTransformer alloc] init] forKey:NSValueTransformerBindingOption];
[self.playPauseTabView bind:#"selectedLabel" toObject:[MyAppState sharedState] withKeyPath:#"playState" options:playStateOptions];
In the NSValueTransformer I return an NSString which must be set in Interface Builder for each tab!

Related

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!

Is there a native function that checks if a value is inside of an enum?

I'm trying to put UILabels inside of an NSDictionary, I'm using tags as key, but the problem is, not all of the labels inside of the view is needed, the tags of the UILabels that is needed is also inside of an enum.
So what I want to do is, check the tag if it exist inside of the enum then add it to dictionary with tag as key.
for (NSObject *obj in [self.formView subviews]) {
if ([obj isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)obj;
// Here is where I want to add the check before I do this line
labelDict[[NSString stringWithFormat:#"%d",label.tag]] = label;
}
}
For Future Readers:
If you are also iterating on an NSArray type of object like the code above, you should use the NSArray function enumerateObjectsUsingBlock instead, this is the answer, doesn't it look more pretty:
[self.formView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)obj;
if ( label.tag >= TEXTFIELDTYPE_MIN_VAL && label.tag <= TEXTFIELDTYPE_MAX_VAL ) {
labelDict[[NSString stringWithFormat:#"%d",label.tag]] = label;
}
}
}];
Objective-C enums are inherited from C enums and cannot be reflected at runtime. Without abusing debug symbols (which would be an overly complicated task for a slow and unreliable result), I believe that it would be impossible to come up with a function that would tell you if an arbitrary value is a member of an arbitrary enum.
One possible workaround would be to create a NSSet that contains all the enum values you have, and check if your label's tag exists within that set. Otherwise, if your enum is sequential, you can check that the tag is between your enum's minimum value and maximum value.
Try this, instead of for loop of all subviews, run for loop of your enum values,
hopefully the enum values are in sequence then only this below code would work:
for(NSInteger tagVal = enum.firstEnum; tagVal <= enum.lastEnum; tagValue++) {
NSObject *obj = [self.formView viewWithTag:tagVal];
if ([obj isKindOfClass:[UILabel class]]) {
UILabel *label = (UILabel *)obj;
// Here is where I want to add the check before I do this line
labelDict[[NSString stringWithFormat:#"%d",label.tag]] = label;
}
}
Try this
if ([[dict allKeys] containsObject:lbl]) {
}

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.

How could I get the property name of the current UITextField a user is in as a string?

I am using the following method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
Before, in order to find the current text field I was in, I was able to write something like this:
if (textField == self.textPlaceID)
{
//Do something
}
Is there a way to grab the name of my textField as a string? I am not asking to take what the user has typed in that textField, I'd like to be able to just get the property name of if. I need it to then concatenate it with some other strings. I am working on making a dynamic method call.
UITextFields do not have "names". You can give your text field a tag and use that.
Also, note that (pointer == pointer) will only return true if you are referencing the same objects, not equivalent values.
Here is how to use the tag: in Interface Builder, give each text field a tag, or if you create your text fields programmatically, set textField.tag = someInt; I usually use macros to make the code more readable:
#define kNameTextField 2
#define kAddressTextField 3
...
if (textField.tag == kNameTextField) ...
With lots of fields like that, I prefer enums:
typedef enum {
kNameTextField = 2,
kAddressTextField,
kPhoneTextField // etc
} Fields;
You can get name of property (in your case textField property) using this code:
-(NSString *)propertyName:(id)property {
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
if ((object_getIvar(self, thisIvar) == property)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}
Remember to import:
#import <objc/runtime.h>
Just call:
NSLog(#"name = %#", [self propertyName:self.textField]);
source
Primarily assign unique tags to each of the textFields (eg 1,2,3.....) avoid 0 for a textField tag
-(void)viewDidLoad{
NSDictionary *dictionary ; //declare it in .h interface
dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"textPlaceID",[NSNumber numberWithInt:1], #"textPlaceID2",[NSNumber numberWithInt:2],nil]; // add textField as object and tag as keys
}
further..
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string{
NSString *textFieldPropertyName = [dictionary objectForKey:[NSNumber numberWithInt:textField.tag]];
}

Get property name as a string

I need a way to pass a property and get the name assigned to it. Any suggestions?
#property (nonatomic, retain) MyObject *crazyObject;
NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return #"crazyObject"
You can try this:
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([self class], &propertyCount);
NSMutableArray * propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
NSLog(#"Names: %#", propertyNames);
It's as simple as this...expanding upon what Chuck already mentioned:
#ifndef STR_PROP
#define STR_PROP( prop ) NSStringFromSelector(#selector(prop))
#endif
You then use it like so:
NSString *strProp = STR_PROP(myProperty);
Background
Keep in mind that properties are really just, to quote Apple, "a syntactical shorthand for declaring a class’s accessor methods." In fact, by itself, the #property declaration doesn't even work. Your #synthesize statement translates the #property into the equivalent of two methods:
- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;
Which one is used depends on the context surrounding your self.crazyObject. (#synthesize also creates a matching instance variable if you didn't do it yourself.) The offshoot of all this is that you can't really translate to and from a property with one single method.
Proposed Solution
You can use what Apple already provides:
NSString *foo = NSStringFromSelector(#selector(myClassProperty));
Or do something custom:
Given that self.crazyObject really translates to either [self crazyObject] or [self setCrazyObject:foo] by the time your code is running, ou'll probably need two methods, like:
- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;
You might then want at least 2 companion methods such as:
- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;
Within these methods, you can use the Foundation functions NSStringFromSelector and NSSelectorFromString to convert back and forth between SEL and NSString. Use whatever string manipulations you like to convert back and forth between your setter string (setCrazyObject) and your property name (crazyObject).
A complete solution is hard to provide without knowing the exact use case, but hopefully this provides some more clues for anyone trying to accomplish something similar. There might even be some useful things made possible by combining this approach with Oscar's answer.
Here is a function that returns the name of an ivar, so basically it not only returns the properties but any ivar of the class. I haven't found a way to get the property directly so I used the ivar trick.
#import <objc/objc.h>
/// -----
- (NSString *)nameOfIvar:(id)ivarPtr
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
id pointer = object_getIvar(self, ivar);
if(pointer == ivarPtr)
{
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
break;
}
}
free(ivars);
}
return name;
}
After searching and debugging i find solution for me...
Added #import <objc/runtime.h>
Methods object_getIvar(id obj, Ivar ivar) send bad access and app crashes. i modify some code and it worked great:
+(NSString*)stringWithProperty:(id)property withClass:(id)controller
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([controller class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([controller valueForKey:name] == property)
{
break;
}
}
free(ivars);
}
return name;
}
Modifying the solution, it works when your object is allocated already, otherwise it returns nil:-
NSString * NSStringFromProperty(NSObject* property, NSObject* class)
{
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([class class], &propertyCount);
NSString *name = nil;
for (unsigned int i = 0; i < propertyCount; ++i)
{
name = [NSString stringWithUTF8String:property_getName(properties[i])];
NSObject *object = [class valueForKey:name];
if (object != nil && object == property)
{
break;
}
else
{
name = nil;
}
}
free(properties);
return name;
}
You can use
NSString *str = NSStringFromSelector(#selector(crazyObject));
The good thing about this approach is that:
Xcode will autocomplete word crazyObject for you.
When later on you will change the property name from crazyObject to myCrazyObject, Xcode will add a warning saying "unrecognized selector!" -- pretty good for debugging.
I use this method so often, that I even created a function, which allows to write less letters:
NSString * __nonnull sfs(SEL __nonnull theSelector)
{
if (!theSelector)
{
abort();
}
return NSStringFromSelector(theSelector);
}
Now your final solution can look like this:
NSString *str = sfs(#selector(crazyObject));
From Get property name as string, without using the runtime reference library, just define:
#define propertyKeyPath(property) (#""#property)
#define propertyKeyPathLastComponent(property) [[(#""#property) componentsSeparatedByString:#"."] lastObject]
And then you can do something like this:
NSLog(#"%#", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street
You may check my approach at Gist to get the string for a property with autocompletion and compile-time check.
How to use:
Get the property name for a class:
#interface AnyClass : NSObject
#property (strong) NSData *data;
#end
// == My approach ==
// C string for a class
PropertyNameForClass(AnyClass, data); // ==> "data"
// NSString for a class
PropertyStringForClass(AnyClass, data); // ==> #"data"
// Bad approach (no autocompletion; no compile-time check):
NSString *propertyName = #"data";
Get the property name for a protocol:
#protocol AnyProtocol
#property (strong) NSDate *date;
#end
// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date); // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date); // ==> #"date"
Unconventional, hacky, ugly, late, but... as strong-named as it gets and works like a charm:
#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:#"."] lastObject] componentsSeparatedByString:#" "] lastObject] stringByReplacingOccurrencesOfString:#"]" withString:#""] : #""
Sample usage:
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname)); // surname
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate])); // birthDate
...