Cocoa binding to single object from an array - cocoa

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

Related

How can I bind to NSTableColumn's headerTitle?

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.

Objective-C EXC_BAD_ACCESS

Ok so I've recently decided to try to teach myself Objective-C (I'm interested in iPhone development), however I've never used C or any of its derivatives before, and as such am running into some problems.
I decided to start out by writing a very basic card application that creates a deck of cards, shuffles the deck, and then displays the cards on the screen using UIButtons, however I'm having a problem with my shuffling algorithm. Every time it gets called I get an EXC_BAD_ACCESS error, which I know means there's something desperately wrong with my code, but I just can't figure out what it is.
- (void) randomize {
NSMutableArray *tmpDeck = [[NSMutableArray alloc] init];
for(Card *tmp in _cards) {
BOOL didInsert = NO;
while(!didInsert) {
NSUInteger random = arc4random_uniform(54);
if([[tmpDeck objectAtIndex:random] isEqual:nil]) {
[tmpDeck insertObject:tmp atIndex:random];
didInsert = YES;
}
}
}
_cards = tmpDeck;
_hasBeenRandomized = YES;
}
_cards is a pointer to an NSMutableArray containing the unshuffled deck of card objects, and _hasBeenRandomized is a boolean (obviously) that keeps track of whether or not the deck has been randomized.
I've tried to use the debugger to work out what exactly is going on here, but I can't even step into the method without my program crashing. This leads me to believe that the problem has to come from the very first line, but it's just a straightforward creation of an NSMutableArray, so I don't know how it could be that. This method is being called from within viewDidLoad. This is the entirety of the viewDidLoad method currently.
- (void)viewDidLoad
{
[super viewDidLoad];
_deck = [[Deck alloc] init];
[_deck randomize];
}
Any and all help will be appreciated. Sorry if the answer is dead obvious.
This is because you are trying to insert into an index that doesn't exist yet. You need to initialize the array with as many places in the array as you need for your cards. Either that or use a NSMutableDictionary and just insert the object with the index being the key.
To add another note, calling initWithCapacity on the array wouldn't solve this for you either since this just gives a "hint" at the size. You need the count property of the array to actually be at least as large as the index you are trying to insert. If you wanted to do an array, then you would first need to populate something in each index first. You could define this in the new array literal format or use a for loop that loops the number of times you need (your max index) and insert a dummy object in it's place.
for (int i=0; i< _cards.count; ++i)
{
[tmpDeck insertObject:#"dummy" atIndex:i];
}
Then instead of checking for 'nil' before you replace, you check if it is equal to the dummy object you inserted. This would give you an array that you can insert into any of these indexes. I personally would still probably store them in an NSMutableDictionary. But if you need it in an array for some other purpose then this is a way to do it.
You also will need to be sure to replace the object instead of inserting, otherwise you will just keep adding indexes.
[tmpDeck replaceObjectAtIndex:random withObject:tmp];
If you still get the same error, set a breakpoint in your debugger and check what the random number is and what the count of your array is. If your random number is ever greater than your array count, then you will get this error.

When to retain and release CGLayerRef?

I have a question similar to this one:
CGLayerRef in NSValue - when to call retain() or release()?
I am drawing 24 circles as radial gradients in a view. To speed it up I am drawing the gradient into a layer and then drawing the layer 24 times. This worked really well to speed up the rendering. On subsequent drawRect calls some of the circles may need to be redrawn with a different hue, while others remain the same.
Every time through drawRect I recalculate a new gradient with the new hue and draw it into a new layer. I then loop through the circles, drawing them with the original layer/gradient or new layer/gradient as appropriate. I have a 24 element NSMutableArray that stores a CGLayerRef for each circle.
I think this is the answer provided in the question I linked above, however it is not working for me. The second time through drawRect, any circle that is drawn using the CGLayerRef that was stored in the array causes the program to crash when calling CGContextDrawLayerAtPoint. In the debugger I have verified that the actual hex value of the original CGLayerRef is stored properly into the array, and in the second time through drawRect that the same hex value is passed to CGContextDrawLayerAtPoint.
Further, I find that if I don't CGLayerRelease the layer then the program doesn't crash, it works fine. This tells me that something is going wrong with the memory management of the layer. It's my understanding that storing an object into an NSArray will increment it's reference count, and it won't be deallocated until the array releases it.
Anyway, here is the relevant code from drawRect. Down at the bottom you can see that I commented out CGLayerRelease. In this configuration the app doesn't crash although I think this is a resource leak. If I uncomment that release then the app crashes the second time though drawRect (between the first and second calls one of the circles has it's led_info.selected property cleared, indicating that it should use the saved layer rather than the new layer:
NSLog(#"ledView drawing hue=%4f sat=%4f num=%d size=%d",hue_slider_value,sat_slider_value,self.num_leds,self.led_size);
rgb_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:1.0];
end_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:0.0];
NSArray *colors = [NSArray arrayWithObjects:
(id)rgb_color.CGColor, (id)end_color.CGColor, nil];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,(__bridge CFArrayRef) colors, NULL);
CGLayerRef layer = CGLayerCreateWithContext(context, (CGSize){self.led_size,self.led_size}, /*auxiliaryInfo*/ NULL);
if (layer) {
CGContextRef layer_context = CGLayerGetContext(layer);
CGContextDrawRadialGradient(layer_context, gradient, led_ctr,self.led_size/8,led_ctr, self.led_size/2,kCGGradientDrawsBeforeStartLocation);
} else {
NSLog(#"didn't get a layer");
}
for (int led=0;led<[self.led_info_array count];led++) {
led_info=[self.led_info_array objectAtIndex:led];
// the first time through selected=1 and led_info.cg_layer=nil for all circles,
// so this branch is taken.
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
led_info.cg_layer=layer;
// the second time through drawRect one or more circles have been deselected.
// They take this path through the if/else
} else {
CGPoint startPoint=led_info.rect.origin;
// app crashes on this call to CGContextDrawLayerAtPoint
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
}
// with this commented out the app doesn't crash.
//CGLayerRelease(layer);
Here is the declaration of led_info:
#interface ledInfo : NSObject
#property CGFloat hue;
#property CGFloat saturation;
#property CGFloat brightness;
#property int selected;
#property CGRect rect;
#property CGPoint center;
#property unsigned index;
#property CGLayerRef cg_layer;
- (NSString *)description;
#end
led_info_array is the NSMutableArray of ledInfo objects, the array itself is a property of the view:
#interface ledView : UIView
#property float hue_slider_value;
#property float sat_slider_value;
#property unsigned num_leds;
#property unsigned led_size;
#property unsigned init_has_been_done;
#property NSMutableArray *led_info_array;
//#property layerPool *layer_pool;
#end
The array is initialized like this:
self.led_info_array = [[NSMutableArray alloc] init];
Edit: since I posted I have found that if I put retain/release around the assignemt into the NSMutableArray then I can also leave in the original CGLayerRelease and the app works. So I guess this is how it is supposed to work, although I'd like to know why the retain/release is necessary. In the objective C book I am reading (and the answer to the question linked above) I thought assigning into NSArray implicitly did retain/release. The new working code looks like this:
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
if (led_info.cg_layer) CGLayerRelease(led_info.cg_layer);
led_info.cg_layer=layer;
CGLayerRetain(layer);
} else {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
You can probably tell that I'm brand new to Objective C and iOS programming, and I realize that I'm not really sticking to convention regarding case and probably other things. I'll clean that up but right now I want to solve this memory management problem.
Rob, thanks for the help. I could use a little further clarification. I think from what you are saying that there are two problems:
1) Reference counting doesn't work with CGLayerRef. OK, but it would be nice to know that while writing code rather than after debugging. What is my indication that when using "things" in Objective C/cocoa that resource counting doesn't work?
2) You say that I'm storing to a property, not an NSArray. True, but the destination of the store is the NSArray via the property, which is a pointer. The value does make it into the array and back out. Does resource counting not work like this? ie instead of CGLayerRef, if I were storing some NSObject into NSArray using the code above would resource counting work? If not, then would getting rid of the intermediate led_info property and accessing the array directly from within the loop work?
You're not storing the layer directly in an NSArray. You're storing it in a property of your ledInfo object.
The problem is that a CGLayer is not really an Objective-C object, so neither ARC nor the compiler-generated (“synthesized”) property setter will take care of retaining and releasing it for you. Suppose you do this:
CGLayerRef layer = CGLayerCreateWithContext(...);
led_info.cg_layer = layer;
CGLayerRelease(layer);
The cg_layer setter method generated by the compiler just stores the pointer in an instance variable and nothing else, because CGLayerRef isn't an Objective-C object reference. So when you then release the layer, its reference count goes to zero and it's deallocated. Now you have a dangling pointer in your cg_layer property, and when you use it later you crash.
The fix is to write the setter manually, like this:
- (void)setCg_layer:(CGLayerRef)layer {
CGLayerRetain(layer);
CGLayerRelease(_cg_layer);
_cg_layer = layer;
}
Note that it's important to retain the new value before releasing the old one. If you release the old one before retaining the new one, and the new one happens to be the same as the old one, you might deallocate the layer right in the middle!
UPDATE
In response to your edits:
Reference counting works with CGLayerRef. Automatic reference counting (ARC) doesn't. ARC only works with things that it thinks are Objective-C objects ARC does not automatically retain and release a CGLayerRef, because ARC doesn't think a CGLayerRef is a reference to an Objective-C object. An Objective-C object is (generally speaking) an instance of a class declared with #interface, or a block.
The CGLayer Reference says that CGLayer is derived from CFType, the basic type for all Core Foundation objects. (As far as ARC is concerned, a Core Foundation object is not an Objective-C object.) You need to read about “Ownership Policy” and “ Core Foundation Object Lifecycle Management” in the Memory Management Programming Guide for Core Foundation.
The “destination of the store” is an instance variable in your ledInfo object. It's not “the NSArray via the property”. The value doesn't ”make it into the array and back out.” The array gets a pointer to your ledInfo object. The array retains and releases the ledInfo object. The array never sees or does anything with the CGLayerRef. Your ledInfo object is responsible for retaining and releasing any Core Foundation objects it wants to own, like the layer in its cg_layer property.
As I mentioned, if ledInfo doesn't retain the layer (with CFRetain or CGLayerRetain) in its cg_layer setter, it risks the layer being deallocated, leaving the ledInfo with a dangling pointer. Do you understand what a dangling pointer is?

performSelector:withObject:afterDelay: - a way to do this to pass something other than an object?

I am trying to use this method to show a second modal after a first modal has been dismissed... The method itself works fine, except that it requires me to pass an object as a parameter and what I really want to do is pass a UIImageControllerSourceType instead. Is there another method I should be using to do this or is there an easy way to make UIImageControllerSourceType an object? I feel like this should not be too hard but I have been struggling with it for awhile now...
- (void)showModalTwoImageSearchViewControllerWithSourceType:(UIImagePickerControllerSourceType *)sourceType {
if (self.modalViewController) {
[self performSelector:#selector(showModalTwoImageSearchViewControllerWithSourceType:)
withObject:sourceType
afterDelay:0.1f];
return;
}
// present second modal view - remembering to deal with cases of camera or photo library
if (sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
//do one thing
}
// lets assume it was UIImagePickerControllerSourceTypeCamera
else {
// do something else
}
}
UIImagePickerControllerSourceType is ultimately a NSUInteger and the method you're trying to do (the performSelector one) only takes Objective C objects in its withObject parameter and not C types or pointers.
You can pass NSUIntegers by creating a NSNumber object and stuffing your integer into that.
To create a NSNumber object, you can create one as easily as NSNumber's numberWithUnsignedInteger method.
UIImagePickerControllerSourceType is an enum. the easiest way to use it as an object is to make it an NSNumber:
NSNumber *n = [NSNumber numberWithInt:sourceType];

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".

Resources