How can I bind to NSTableColumn's headerTitle? - macos

I would like to bind NSTableColumn's headerTitle property to an NSMutableArray in my model layer (via an NSArrayController).
Basically I want to have an array where I can change values and have the table column header titles update. Is that reasonable?
However, the headerTitle binding wants an single NSString and I'm not sure how to connect my model object to this binding via my NSArrayController. Google does not give many hits for this problem.
My model layer consists of two class (both of which are appropriately KVC compliant). The first is a model which represents a single column title, it has one property title,
// A model class representing the column title of single NSTableColumn
#interface ColumnTitle : NSObject
#property NSString *title;
+ (ColumnTitle*) columnTitleWithTitle:(NSString*) aString;
#end
The second a model object which represents an ordered group of ColumnTitle objects,
// Class representing an order collection of model items
#interface TableColumnTitles : NSObject
#property NSMutableArray* columnTitles; // an array of ColumnTitle objects
// These are the KVC array accessors
-(void) insertObject:(ColumnTitle*)columnTitle inColumnTitlesAtIndex:(NSUInteger)index;
- (void)removeObjectFromColumnTitlesAtIndex:(NSUInteger)index;
- (void)replaceObjectInColumnTitlesAtIndex:(NSUInteger)index withObject:(ColumnTitle*)columnTitle;
#end
Note that TableColumnTitles object implements the above array accessors which are required for the bindings. Any suggestions?

Haven't tried that before but what you're actually asking for is using KVC for array indexes. A quick google didn't turn up anything on that issue except some results that indicate it's not (yet) possible (check this)
The easiest work-around I could come up with would be to simply add dedicated properties for the array indexes.. not nice but does the job.
So for a NSMutableArray called myArray and contains objects with title properties of type NSString you'd do something like:
#property (nonatomic, readonly, getter = columnOneGetter) NSString *columnOneString;
(NSString*) columnOneGetter
{
return myArray[0].title;
}
Always assuming of course their number is known in advance and we're not talking 200 columns :-)

I think this may/may not be what you're after, but quick google search landed me here:
http://pinkstone.co.uk/how-to-add-touch-events-to-a-uitableviewfooter-or-header/
edit: i realize this is for mac (not ios) but should be pretty easy to translate if it actually helps.

Related

The Idea? creating sorted array from NSDictionary key values (with objects)

I know there are a whole bunch of questions that have been asked and answered in stackoverflow about the challenge of getting keys in an NSDictionary sorted by putting those keys into sort order in an array. I understand that objects are not stored in sort order within the actual dictionary and that is, I think, for reasons of efficiency or maybe memory management on the part of Foundation code.
I have been working on trying out examples from several answers out here and in apple documentation and blogs (some I can get to work, others not!) , but I can't seem to find an example that solves my confusion.
I think my confusion is that the examples I'm encountering both here, in apple documentation and in the different helpful blogs, all seem to have examples where there is just a key value pair and the second value is not an object - it's more like just a value. (However isn't it really an object at some level? I would think it is)
One example, that I couldn't get to work (Sorting an NSArray by an NSDictionary value ) , uses this idea
[array sortedArrayUsingComparator:^(NSDictionary *item1, NSDictionary *item2) {
NSString *age1 = [item1 objectForKey:#"age"];
NSString *age2 = [item2 objectForKey:#"age"];
return [age1 compare:age2 options:NSNumericSearch];
}];
I thought maybe this idea, specifying the key in a more specific manner, might be my problem.
I wonder if maybe I'm not communicating to the compiler what the key is, and what the object is, and that is why I'm getting an "unrecognized selector sent to instance" error.
..... Code Snips Follow .....
1)
I have a class called "Dog". A given dog object has several properties, including an NSString key.
My key is "licenseString" is an alphanumeric key - I'm also wondering if I should use decimalNumberWithString but that's not the question here
#property (strong,nonatomic) NSString *licenseString;
#property (strong, nonatomic) NSString *dogName;
#property (strong, nonatomic) NSString *whatMakesDogSpecial;
#property (strong, nonatomic) UIImage *dogPhoto;
2) I have an NSDictionary
#property (nonatomic, strong) NSDictionary *dogDictionary;
I hardcode information into the dogDictionary in this not very sophisticated way,
Dog *aDog;
// Dog one
aDog = [[Dog alloc]init] ;
aDog.licenseString = #"1";
aDog.dogName = #"Oscar";
aDog.whatMakesDogSpecial = #"This animal was found at the Mid-Penn humane society. He is super friendly, plays well with other dogs and likes people too. He enjoys fetching balls and frisbees too. He also goes to the park daily." ;
aDog.dogPhoto = [UIImage imageNamed:#"webVuMiniAppDogOscar.jpg"];
[self.dogDictionary setValue:aDog forKey:aDog.licenseString];
3) Then once I have several dog objects in my dogDictionary, I want to sort on the license tag values, so that I can populate a table view with dog names, but by order of their license tags.
BTW it seems that the compiler does recognize "vars.dogDictionary" which appears in the code snip below, because when I look at the debugger I can see that two valid instances are coming up from my dog dictionary. The debugger output is in an attachment
So, using ideas from a stackoverflow answer and the apple documentation, I write this
NSArray *sortedKeys = [vars.dogDictionary keysSortedByValueUsingComparator:
^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2];
}];
NSLog(#" The sorted array is %#", sortedKeys);
And that's where my problem happens. I recognize that 0x1182f740 refers to "obj1" as shown in the debugger attachment
2013-08-06 15:13:58.276 SortDogLIcenseTags[3876:11303] -[Dog compare:]: unrecognized selector sent to instance 0x1182f740
(lldb)
Attachment is a picture showing debugger values - they don't like to paste very well
Here's how I resolved this challenge. It works and was pretty straightforward to integrate into my Master/Detail project
I know I found a tutorial on the web somewhere that led me to this solution , I'm sorry I can't find it now.
Note that sortedDogDictionaryArray and dogDictionaryArray are declared as properties in the .h file.
self.dogDictionaryArray = [vars.dogDictionary allValues];
// Sort
NSSortDescriptor *sortDescriptorDog =
[[NSSortDescriptor alloc] initWithKey:#"licenseString" ascending:YES];
NSArray *sortDescriptorsDogs =
[NSArray arrayWithObject:sortDescriptorDog];
self.sortedDogDictionaryArray =
[self.dogDictionaryArray sortedArrayUsingDescriptors:sortDescriptorsDogs];
NSLog(#"%#",self.sortedDogDictionaryArray );
int doggie;
Dog *someDogName;
NSLog(#"Sorted Order is...");
for (doggie = 0; doggie < [self.sortedDogDictionaryArray count]; doggie++) {
//NSLog(#"%#", [sortedArray objectAtIndex:i]);
//NSLog(#"%#", [sortedArrayDogs objectAtIndex:doggie]);
someDogName = [self.sortedDogDictionaryArray objectAtIndex:doggie];
//NSLog(#"name is %#", someDogName.dogName);
NSLog(#"name is %# tag is %#", someDogName.dogName, someDogName.licenseString);
}

NSArrayController reallocating array?

I have an NSCollectionView whose content is bound to an NSArrayController's arrangedObjects. When I call addObject: on the array controller, it seems to reallocate the underlying array - I can observe the pointer changing addresses. This isn't acceptable behavior for my particular case, as other objects also depend on the array.
Is this normal behavior or am I doing something wrong? I've seen some alternative solutions, such as directly modifying the array and calling willChangeValueforKey: and didChangeValueForKey: on the controller, but that doesn't seem like the most elegant solution.
I am fairly new to Objective-C and Cocoa, so go easy on me. :)
Thanks!
#interface NSArrayController : NSObjectController {
#private
void *_reserved4;
id _rearrangementExtensions;
NSMutableArray *_temporaryWorkObjects;
NSUInteger _observedIndexHint;
NSMutableIndexSet *_selectionIndexes;
NSMutableArray *_objects;
NSIndexSet *_cachedSelectedIndexes;
NSArray *_cachedSelectedObjects;
NSArray *_arrangedObjects;
}
If you look at NSArrayController's header, the instance variable for arrangedObject is a NSArray, so it will have to create a new array whenever you add a new object and is a normal behaviour.

Iterate over NSTableview or NSArrayController to get data

I have an NSTableview which s bound to a NSArrayController. The Table/Arraycontroller contains Core Data "Person" entities. The people are added to the NSTableview by the GUI's user.
Let's say a person entity looks like
NSString* Name;
int Age;
NSString* HairColor;
Now I want to iterate over what is stored in the array controller to perform some operation in it. The actual operation I want to do isn't important I don't really want to get bogged down in what I am trying to do with the information. It's just iterating over everything held in the NSArraycontroller which is confusing me. I come from a C++ and C# background and am new to Cocoa. Let's say I want to build a NSMutableArray that contains each person from nsarraycontroller 1 year in the future.
So I would want to do something like
NSMutableArray* mutArray = [[NSMutableArray alloc] init];
foreach(PersonEntity p in myNsArrayController) // foreach doesn't exist in obj-c
{
Person* new_person = [[Person alloc] init];
[new_person setName:p.name];
[new_person setHairColor:p.HairColor];
[new_person setAge:(p.age + 1)];
[mutArray addObject:new_person];
}
I believe the only thing holding me back from doing something like the code above is that foreach does not exist in Obj-c. I just don't see how to iterate over the nsarraycontroller.
Note: This is for OSX so I have garbage collection turned on
You're looking for fast enumeration.
For your example, something like
for (PersonEntity *p in myNsArrayController.arrangedObjects)
{
// Rest of your code
}
You can also enumerate using blocks. For example:
[myNsArrayController enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop)
{
PersonEntity *p = object;
// Rest of your code
}];
There's pro's and cons to both approaches. These are discussed in depth in the answer to this question:
Objective-C enumerateUsingBlock vs fast enumeration?
You can find a great tutorial on blocks in Apple's WWDC 2010 videos. In that they say that at Apple they use blocks "all the time".

How can NSArrayController sort on a to-many relationship?

Using CoreData, I have an entity "Bookmark", that has an to-many relationship named 'tags' to another entity "Tag", and some commun attributes (string, date, ...).
In a NSTableView we display the Bookmarks entity via Binding:
the NSArrayController is binded to File's Owner.managedObjectContext
(standard XCode CoreData template, the managedObjectContext is in the AppDelegate)
The columns in TableView are binded to their respective attribute. In particular the Tag column is binded to this arrayController.arrangedObjects.tags with a subclass of NSValueTransformer so that we can show, as an NSString, a summary of the to-many relationship.
It work. Now when I click on column header the whole table view get sorted correctly except for the 'tag' column where I get this:
-[_NSFaultingMutableSet compare:]: unrecognized selector sent to instance
For sure the "Set" from this to-many relationship doesn't respond to the selector 'compare:'.
Question:
How can I make this work ? How can I sort on a to-many relationship ?
Are something like the ValueTransformer available ? If I could supply a custom class that would do the compare: for the ArrayController to know...
One possible hack: since _NSFaultingMutableSet is a NSSet, we can add the selector 'compare:' via a categorie.
#interface NSSet (someAdditions)
- (NSComparisonResult)compare:(NSSet *)anotherSet;
#end
#implementation NSSet (someAdditions)
- (NSComparisonResult)compare:(NSSet *)aSet {
...
}
#end
we can now implement this compare: selector as we wish, like comparing the count of each set, or their NSString representation in some way.
It work in my App. I re-enabled the 'Creates Sort Descriptor' on the binding of the NSTableColumn and can now click on the header of my tableView to sort.
It's a hack because it affect all NSSet... But at least I have my hook.
What do you think ?

Cocoa binding to single object from an array

I previously posted this question as a comment on a related thread thinking it was simple. That thread is here:
Cocoa binding to a particular item in an array controller
The questions relates to (and I'll more fully describe it here) a game I'm building to try and learn objective-c and cocoa. Its good enough to think of it like texas hold-em poker. One server holds the game information and manages input from a variable number of clients (always more than one). Through cocoa bindings, it displays to each player the public information of the game which is stored in an array on the server using an array controller in IB. Think of the five cards on the table being stored in an NSArray on the server and bound to the content field of an NSArrayController for each client.
This part works fine, like a charm. However, each player has two cards that he needs to keep private. Each client should display a different card depending on what was dealt to that particular player. (Because what is really happening is I'm binding to an array of player objects
NSArray * thePlayers,
imagine all the cards being stored on the same array). So my question is, how do I set up bindings to a single object out of the array controller (or do I need some other controller)? That is, how to I bind to one player of thePlayers array?'
You set up a property in the controller or model to access that particular player and bind to that. There is no way to bind directly to an object at a particular index in an array.
If you do want to bind to specific array indices, you could could create a wrapper object. Something like this. It lets you bind to item0, item1 and so on. There is no range checking and it breaks if you change the size of the array, but you get the idea.
Interface
#interface MyArrayBinder : NSObject {
NSMutableArray *array;
}
- (id)initWithMutableArray:(NSMutableArray *)theArray;
- (NSMutableArray *)array;
#end
Implementation
#include <objc/runtime.h>
static NSInteger _indexFromSelector(SEL sel) {
return [[NSStringFromSelector(sel) stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] integerValue];
}
static void _dynamicSetItem(MyArrayBinder *self, SEL sel, id obj) {
[self.array replaceObjectAtIndex:_indexFromSelector(sel) withObject:obj];
}
static id _dynamicItem(MyArrayBinder *self, SEL sel) {
return [self.array objectAtIndex:_indexFromSelector(sel)];
}
#implementation MyArrayBinder
- (id)initWithMutableArray:(NSMutableArray *)theArray {
self=[super init];
if (self) {
array=theArray;
for(NSUInteger i=0; i<[array count]; i++) {
class_addMethod([self class], NSSelectorFromString([NSString stringWithFormat:#"item%lu", i]), (IMP) _dynamicItem, "##:");
class_addMethod([self class], NSSelectorFromString([NSString stringWithFormat:#"setItem%lu:", i]), (IMP) _dynamicSetItem, "v#:#");
}
}
return self;
}
- (NSMutableArray *)array {
return array;
}
#end
However, each player has two cards that he needs to keep private. Each client should display a different card depending on what was dealt to that particular player. (Because what is really happening is I'm binding to an array of player objects …
The client knows which player it's representing, right? Not by index—it should have a direct reference to the Player object for the player sitting at its keyboard. Something like MyPlayer *userPlayer;. This is in addition to the dealer object holding an array of all the players, including that one.
Once you have it lain out that way, with the client controller having a property whose value is the user's Player object, the binding becomes simple: You'll bind the card views directly to card A and card B of the userPlayer property of the client controller. (This is essentially what Chuck already suggested in his answer, and what I suggested in my comment on your answer on that other question.)
imagine all the cards being stored on the same array).
Why would I want to imagine that? Why don't the players own their own cards separately?
OK, so the dealer should own all the cards (that is, the deck). It should co-own those also held by a player. The players don't access their cards through the dealer; each player should directly hold his or her cards.
It sounds like you made the same mistake with cards as with players: Thinking that one object can/should know another through an array by index. You can't—certainly not if you want to use that knowledge with Bindings—and shouldn't. The one object needs to know the other directly. This is not only the correct solution, it's the correct way for objects to know each other. Any array-index-based reference would be more complex for no benefit.
Very similar to Nick Moore's solution:
If you do want to bind to specific array indices, you could create a wrapper object. Something like this. It lets you bind to item0, item1 and so on. There is no range checking and it breaks if you change the size of the array, but you get the idea.
Interface
#interface MyArrayBinder : NSObject
#property NSMutableArray *array;
- (id)initWithMutableArray:(NSMutableArray *)theArray;
#end
Implementation
static NSInteger _indexFromString(NSString *key) {
return [[key stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] integerValue];
}
#implementation MyArrayBinder
- (id)initWithMutableArray:(NSMutableArray *)theArray {
if ( self=[super init] ) {
_array=theArray;
}
return self;
}
- (id)valueForUndefinedKey:(NSString *)key {
return _array[_indexFromString( key )];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
_array[_indexFromString( key )] = value;
}
#end

Resources