Why doesn't my KVO dependency not work in NSArrayController - cocoa

I would like to use an NSArrayController with an NSTableView to allow multiple selection but only provided a selected object when a single object is selected (and nil when none or multiple are selected).
I've attempted to implement this with a category on NSArrayController, as shown here:
#implementation NSArrayController (SelectedObject)
+ (NSSet *)keyPathsForValuesAffectingSelectedObject {
return [NSSet setWithObject:#"selection"];
}
- (id)selectedObject {
// Get the actual selected object (or nil) instead of a proxy.
if (self.selectionIndexes.count == 1) {
return [self arrangedObjects][self.selectionIndex];
}
return nil;
}
#end
For some reason, the selectedObject method is not called when the selection of the array controller changes (and something else is observing selectedObject). Why is this?

The selection property of NSArrayController is strange voodoo. I don't know if key-value observing it (and not a path that goes through it) produces change notifications when the selection changes. After all, it returns a proxy and there's no reason to believe that the identity of that proxy changes over time.
In any case, your actual selectedObject method doesn't actually use selection (and it shouldn't). It uses arrangedObjects and selectionIndexes. So, you should return a set containing those keys from +keyPathsForValuesAffectingSelectedObject.
Of course, if you're using a view-based table, you need to make sure the table view's selectionIndexes binding is bound to the array controller's selectionIndexes property, or the array controller just won't know anything about the selection in the table view. (For cell-based table views, you'd typically bind the columns to the array controller and the table view would automatically bind its own bindings based on the columns' bindings.)
Finally, I think you should choose a different name for selectedObject. It's too likely that Apple has a private method of that name or will add one in the future.

I managed to get this working by creating a subclass of NSArrayController and manually observing the selectionIndexes key. I'd prefer to do it using a category but this does appear to work.
static NSString *const kObservingSelectionIndexesContext = #"ObservingSelectionIndexesContext";
#implementation BetterArrayController
- (void)awakeFromNib {
[super awakeFromNib];
[self addObserver:self forKeyPath:#"selectionIndexes" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:(void *)&kObservingSelectionIndexesContext];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:#"selectionIndexes" context:(void *)&kObservingSelectionIndexesContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (void *)&kObservingSelectionIndexesContext) {
[self willChangeValueForKey:#"selectedObject"];
[self didChangeValueForKey:#"selectedObject"];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (id)selectedObject {
// Get the actual selected object (or nil) instead of a proxy.
if (self.selectionIndexes.count == 1) {
return [self arrangedObjects][self.selectionIndex];
}
return nil;
}
#end
I used a context (as per this article) to avoid removing any observers the superclass may have in dealloc (as cautioned against here).

Related

NSCollectionView not updating when adding objects

I have an NSCollectionView with an array controller that is successfully showing objects after they are added and the application is restarted, but not when immediately when added.
I have a controller class which is a subclass of NSObject that reads data from a plist into an NSMutableArray, which is bound to an Array Controller, which is in turn bound to my NSCollectionView. I believe my bindings are correct, as if I add an object, after I restart my application, everything shows up fine, including the new object and all the bound attributes. But when I add an object, it won't be added immediately. The application needs to be restarted. I believe that since my bindings appear to be correct, this is an issue with my controller class not being Key-Value compliant.
I have implemented all of the methods I believe I should have, as per the "Key-Value Coding Accessor Methods" section of the Key-Value Coding programming guide. I believe I have implemented each of the required accessors in the [Collection Accessor Patterns for To-Many Properties][1] section. Furthermore, in the [Quick Start for Collection Views][2], which I have completed, it states not even all of these methods need to be implemented (which I have confirmed).
Here are some code samples to better explain what I am doing.
My collection class, "MYCollection":
#import <Foundation/Foundation.h>
#import "MyObject.h"
#interface MYCollection : NSObject
#property (retain, readwrite) NSMutableArray* objects;
- (void)insertObject:(MYObject *)object inObjectsAtIndex:(NSUInteger)index;
#end
#import "MYObjectCollection.h"
#import "MYObject.h"
#implementation MYObjectCollection
#synthesize objects = _objects;
- (id)init {
self = [super init];
if (self) {
_objects = [self objects];
}
return self;
}
- (NSArray*)objects {
// here I retrieve the objects from the plist into a mutable array
// let's call that array "sortedArray"
return sortedArray;
}
- (void)setObjects:(NSMutableArray *)objectsArray {
// here I write the object array to a plist
_objects = objectsArray;
}
-(void)insertObject:(MYObject*)object inObjectsAtIndex:(NSUInteger)index {
[_objects insertObject:object atIndex:index];
[self setObjects:_objects];
return;
}
-(void)addObjectsObject:(MYObject*)object {
[_objects addObject:object];
[self setObjects:_objects];
return;
}
-(void)removeObjectFromObjectsAtIndex:(NSUInteger)index {
[_objects removeObjectAtIndex:index];
[self setObjects:_objects];
return;
}
-(void)removeObjectsObject:(MYObject*)object {
[_objects removeObject:object];
[self setObjects:_objects];
return;
}
-(id)objectInObjectsAtIndex:(NSUInteger)index {
return [_objects objectAtIndex:index];
}
-(NSUInteger)countOfObjects {
return [_objects count];
}
- (NSEnumerator *)enumeratorOfObjects {
return [_objects objectEnumerator];
}
#end
I am adding objects to this controller by means of an external view, elsewhere:
MYObjectCollection *collection = [[MYObjectCollection alloc] init];
[collection insertObject:new inObjectsAtIndex:[collection.objects count]];
I'm not sure how to continue troubleshooting this issue. I believe that my bindings are correct and I think I have implemented all of the necessary methods for Key-Value coding, but maybe I haven't, or maybe they're wrong. Any help would be appreciated.

NSCollectionView Drag Drop example

I am trying to implement drag drop in NSCollectionView which will allow to re arrange cells in view. I have set the delegate and implemented below methods :
-(BOOL)collectionView:(NSCollectionView *)collectionView writeItemsAtIndexes:(NSIndexSet *)indexes toPasteboard:(NSPasteboard *)pasteboard {
NSLog(#"Write Items at indexes : %#", indexes);
return YES;
}
- (BOOL)collectionView:(NSCollectionView *)collectionView canDragItemsAtIndexes:(NSIndexSet *)indexes withEvent:(NSEvent *)event {
NSLog(#"Can Drag");
return YES;
}
- (BOOL)collectionView:(NSCollectionView *)collectionView acceptDrop:(id<NSDraggingInfo>)draggingInfo index:(NSInteger)index dropOperation:(NSCollectionViewDropOperation)dropOperation {
NSLog(#"Accept Drop");
return YES;
}
-(NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id<NSDraggingInfo>)draggingInfo proposedIndex:(NSInteger *)proposedDropIndex dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation {
NSLog(#"Validate Drop");
return NSDragOperationMove;
}
I am not sure how to take this further. With this I can see that now I can drag around the individual Collection Item but how can I make the Drop ?
You have only implemented the delegate methods but there s no logic in some of the methods. For example to drag around a Collection Item I would add below logic :
-(BOOL)collectionView:(NSCollectionView *)collectionView writeItemsAtIndexes:(NSIndexSet *)indexes toPasteboard:(NSPasteboard *)pasteboard {
NSData *indexData = [NSKeyedArchiver archivedDataWithRootObject:indexes];
[pasteboard setDraggedTypes:#[#"my_drag_type_id"]];
[pasteboard setData:indexData forType:#"my_drag_type_id"];
// Here we temporarily store the index of the Cell,
// being dragged to pasteboard.
return YES;
}
- (BOOL)collectionView:(NSCollectionView *)collectionView acceptDrop:(id<NSDraggingInfo>)draggingInfo index:(NSInteger)index dropOperation:(NSCollectionViewDropOperation)dropOperation {
NSPasteboard *pBoard = [draggingInfo draggingPasteboard];
NSData *indexData = [pBoard dataForType:#"my_drag_type_id"];
NSIndexSet *indexes = [NSKeyedUnarchiver unarchiveObjectWithData:indexData];
NSInteger draggedCell = [indexes firstIndex];
// Now we know the Original Index (draggedCell) and the
// index of destination (index). Simply swap them in the collection view array.
return YES;
}
You also need to register the collection view to drag type in awakefromnib as
[_myCollectionView registerForDraggedTypes:#[#"my_drag_type_id"]];
And make sure that you have set the collection view as selectable.
In addition to what GoodSp33d mentions above, you're also missing the validate delegate function which is required to accept drops. In Swift this is:
func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionViewDropOperation>) -> NSDragOperation
Note the return value, NSDragOperation. This method should contain code that determines precisely what kind of drag operation is being attempted and returns this value. Returning the wrong thing can lead to some pretty annoying bugs.
Further note that in order to support this kind of operation, the collection view layout class you are using must also support drag and drop. Flow layout should do this out-of-the-box, but if you're using a custom layout you may need to adapt it to support drag-and-drop so that the collection view can detect valid drop targets and determine a suitable index path for them.

How to implement drag and drop in an NSOutlineView using bindings?

I'm trying to implement drag and drop in my NSOutlineView but none of the example code, tutorials or other SO questions that I've found seem to work for my situation. I have an NSOutlineView with its content bound to an NSTreeController. The tree controller's Content Array is bound to an NSMutableArray of custom objects that have childern objects of the same type. In the outline view I can add and remove objects at any level in the heirarchy. So far so good.
To implement drag and drop I created and NSObject sublass that will serve as the outline view's dataSource. I have implemented a few methods, based on sample code and posts I found on Stack Overflow. I can initiate a drag, but when I do the drop, outlineView: acceptDrop: item: childIndex: is called but all of the values except for childIndex: are nil. The value for childIndex tells me the index of the drop location within the array but not which node I am at within the heirarchy.
I assume that all the other values passed in outlineView: acceptDrop: ... are nil because I haven't fully implemented dataSource, I'm only using it to control the drag and drop operation. Do I need to set up more pasteboard information when I start the drag? How do I find out what node I'm at when the drop occurs? Why are all the values in outlineView: acceptDrop: ... nil?
Here is the implementation of the outline views dataSource:
\ #implementation TNLDragController
- (void)awakeFromNib {
[self.titlesOutlineView registerForDraggedTypes:[NSArray arrayWithObject:#"Event"]];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard {
NSLog(#"starting a drag");
NSString *pasteBoardType = #"Event";
[pboard declareTypes:[NSArray arrayWithObject:pasteBoardType] owner:self];
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView
validateDrop:(id < NSDraggingInfo >)info
proposedItem:(id)item
proposedChildIndex:(NSInteger)index {
NSLog(#"validating a drag operation");
return NSDragOperationGeneric;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index {
NSLog(#"accepting drag operation");
//todo: move the object in the data model;
NSIndexPath *path = [self.treeController selectionIndexPath]; // these three values are nil too.
NSArray *objects = [self.treeController selectedObjects];
NSArray *nodes = [self.treeController selectedNodes];
return YES;
}
// This method gets called by the framework but the values from bindings are used instead
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return NULL;
}
/*
The following are implemented as stubs because they are required when
implementing an NSOutlineViewDataSource. Because we use bindings on the
table column these methods are never called. The NSLog statements have been
included to prove that these methods are not called.
*/
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
NSLog(#"numberOfChildrenOfItem");
return 1;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
NSLog(#"isItemExpandable");
return YES;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
NSLog(#"child of Item");
return NULL;
}
#end
the implementation I described in this question was, in fact working just fine, but I made a rookie mistake when trying to determine if it was working. I set a breakpoint in - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index in order to examine the values that were being passed into the method. I'm using ARC and the values were never referenced within the method so ARC never retained them, making them unavailable to the debugger!

Cocoa-bindings and KVO

I have a view MyView, and it has images which I want to bind with an array in my AppDelegate.
MyView class
#interface MyView : NSView {
#private
NSArray *images;
}
#end
+ (void)initialize
{
[self exposeBinding:#"images"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"Changed!");
}
My AppDelegate
#property (retain) NSArray *images;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
images = [[NSMutableArray alloc] init];
[view bind:#"images" toObject:self withKeyPath:#"images" options:nil];
// [self addObserver:view forKeyPath:#"images" options:0 context:nil]; // !!!
MyImage *img = [[MyImage alloc] ...];
[self willChangeValueForKey:#"images"];
[[self images] addObject:img];
[self didChangeValueForKey:#"images"];
[img release];
}
Without [self addObserver:view forKeyPath:#"images" options:0 context:nil]; the method observeValueForKeyPath: is never called.
Is it necessary to call addObserver: when using bind:? Does bind: set the KVO? And why doesn't binding work?
What you need is an implemented setter for the images property like below. The most common use-case for this is that you need to invalidate the drawing and request redraw with
-setNeedsDisplay:YES.
- (void)setImages:(NSArray *)newImages
{
if(newImages != images) {
[images release];
images = newImages;
[images retain];
}
[self setNeedsDisplay:YES]; // Addition and only difference to synthesized setter
}
You can drop the -exposeBinding: call, since that has only influence on plugins for Interface Builder, and those where lost with the introduction of Xcode 4.
The reason why the -observeValueForKeyPath:ofObject:change:context: message is not send is that for a binding the observer is not the bound-to object. There is another object in the background. (In the stack form a breakpoint you can see that its class is NSEditableBinder.) So it is correct to register as observer from within the view to the view property #"images".
Another way to get notified about a change in the view is to override -setValue:forKey: method. Then you would need to check the key string and see if it was equal to #"images". But since there are other methods from the KVC protocol like -setValue:forKeyPath:, you would need to be extra careful to not disturb the machinery, i.e. always call super.
Uh. I just realize that my answer so far assumes the easier case where you replace the whole array. Your question was for an array modification. (You do declare an immutable array property in your example, though, which only allows replacement. So keep it as declared, and my approach so far will work. Below I show the other alternative.)
Ok, lets assume you do this in the app delegate, a replacement:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{  
    [view bind:#"images" toObject:self withKeyPath:#"images" options:nil];
    MyImage *img = [[MyImage alloc] ...];
    self.images = [NSArray arrayWithObject:img];
    [img release];
}
You don't need to post the change (using willChangeValueForKey: and didChangeValueForKey:, since you go through the declared property. They do that for you.
Now to the other approach where you modify an array. For that you need to use a mutable array property and modify it through an KVO-notifying proxy, like this:
[self mutableArrayValueForKey:#"images"] addObject:img];
This would pick up the change on the sending (bound-to) side. Then it would be transported to the view through the binding machinery, and eventually set using KVC.
There, on the receiving end in the view, you would need to pick up the property change to #"images". That could be done by overwriting the collection accessor method(s) and do more work there, instead of just accepting the the change. But that is a bit complicated, since there are quite a few accessor methods (See docs). Or, simpler, you could add another observation relationship from within the view.
For that, somewhere in initialization (-awakeFromNib: for example) of the view:
[self addObserver:self forKeyPath:#"images" options:0 context:nil];
and then:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
if([keyPath isEqualToString:#"images"]) {
[self setNeedsDisplay:YES]; // or what else you need to do then.
}
}
Note that this last observer relationship has nothing to do with the binding any longer. The value change to the bound property properly arrives at the view without, you just don't realize (get notified).
That should work.
The only way to have observeValueForKeyPath called is to call addObserver. Binding works through a different mechanism.

Tableview with 2 columns y=mx example

How would I make a tableview with 2 columns (x and y) for a function of y=mx
Ive tried lots of things, all of which end in complete failure.
Can someone please make me a sample code and explain it.
Ive asked and asked and people have directed me to all sorts of tutorials of bool, and how to copy and paste contents, how to save to a file, how to make a list of open applications, none of which help me because they are overly complicated
i have this
//array.m
#import "array.h"
#implementation array
- (IBAction)makeArrays:(id)sender
{
int x,y;
NSNumber *multiplier=[NSNumber numberWithFloat:[mField floatValue]];
for (x=0;x++;x<181)
{
y=[multiplier floatValue]*x;
NSNumber *xValue = [NSNumber numberWithInt:x];
NSNumber *yValue = [NSNumber numberWithInt:x];
NSArray *xArray = [NSArray arrayWithObject:xValue];
NSArray *yArray = [NSArray arrayWithObject:yValue];
}
}
#end
and class file
//array.h
#import <Cocoa/Cocoa.h>
#interface array : NSObject {
IBOutlet id mField;
}
- (IBAction)makeArrays:(id)sender;
#end
where do i go from here?
The first thing you should do in OOP is consider the classes of objects. Cocoa uses an MVC (Model, View, Controller) architecture, so classes should fit in one of these three categories. Cocoa already provides the NSTableView class which works quite well, so that leaves the model and controller.
There are a number of different approaches to the model class you could take:
You could write a function table class that holds x and y values in separate arrays
You could write a function table class that has a single array of (x,y) pairs.
In either this or the previous implementation, you could provide a public interface that supports both arrangements (i.e. they'd have methods that return a y given an x, and properties that are x, y, and (x,y) collections). Some implementation details would depend on how you're connecting the table view to the data (bindings, or the older NSTableViewDataSource protocol).
You could also use an array of x values, and create a value transformer. With this approach, the y-values exist in the table view and not the model.
And so on
The application requirements will determine which approach to take. I'll show you the value transformer approach, as it requires the least amount of code.
For the controller, you could rely on NSArrayController (which works quite well with NSTableView), or create your own. For example, you could use an NSMutableArray as the model, and create a controller that maps the values from the array to other values. This controller could perform the mapping using blocks or some function classes that you define.
As you see, there are quite a few options. I'm going to go with the option that requires the least coding: a value transformer, an NSArrayController for the controller and an NSMutableArray (stored in an object that also stores a value transformer) for the model. In the following, code should be stored in files following the standard convention: each interface and implementation is in a separate file with name equal to the class, and an extension of ".h" for interfaces and ".m" for implementation. I also won't bother with the common import statements, such as for Cocoa/Cocoa.h and each class implementation's own interface.
First, the value transformer. Actually, there are two, an abstract superclass and a concrete subclass. This separation is so that you can easily add other function types later. The superclass, FunctionTransformer, is very simple. All that needs to be overridden from its base, NSValueTransformer, is the method that returns the class of transformed values, transformedValueClass:
#interface FunctionTransformer : NSValueTransformer
+ (Class)transformedValueClass;
#end
#implementation Function
+ (Class)transformedValueClass {
return [NSNumber class];
}
#end
The concrete subclass, LinearTransformer, needs to override the primary method of value transformers: transformedValue:. Since linear transforms are invertible, we'll also provide a reverseTransformedValue:. It will also need properties for the slope and intercept values.
#import "FunctionTransformer.h"
#interface LinearTransformer : FunctionTransformer {
NSNumber *m_;
NSNumber *b_;
}
#property (nonatomic,retain) NSNumber *slope;
#property (nonatomic,retain) NSNumber *intercept;
+ (BOOL)allowsReverseTransformation;
-(id)init;
-(id)initWithSlope:(float)slope;
-(id)initWithIntercept:(float)intercept;
-(id)initWithSlope:(float)slope intercept:(float)intercept;
-(void)dealloc;
-(NSNumber*)transformedValue:(id)value;
-(NSNumber*)reverseTransformedValue:(id)value;
#end
#implementation LinearTransformer
#synthesize slope=m_, intercept=b_;
+(BOOL)allowsReverseTransformation {
return YES;
}
-(id)initWithSlope:(float)m intercept:(float)b {
if ((self = [super init])) {
m_ = [[NSNumber alloc] initWithFloat:m];
b_ = [[NSNumber alloc] initWithFloat:b];
}
return self;
}
-(id)init {
return [self initWithSlope:1.0 intercept:0.0];
}
-(id)initWithSlope:(float)slope {
return [self initWithSlope:slope intercept:0.0];
}
-(id)initWithIntercept:(float)intercept {
return [self initWithSlope:1.0 intercept:intercept];
}
-(void)dealloc {
[b release];
[m release];
[super dealloc];
}
-(NSNumber*)transformedValue:(id)value {
return [NSNumber numberWithFloat:([value floatValue] * [m floatValue] + [b floatValue])];
}
-(NSNumber*)reverseTransformedValue:(id)value {
return [NSNumber numberWithFloat:(([value floatValue] - [b floatValue]) / [m floatValue])];
}
#end
A specific LinearTransformer needs to be registered to be used so that you can set the slope and intercept. The application delegate could own this transformer (along with the x value collection), or you could write a custom controller. We're going to write a model class that bundles together the x values and the value transformer, named FunctionTable. Setting the function transformer requires a sub tasks: registering the transformer as a value transformer (using +setValueTransformer:forName:). This means we'll need to provide our own setter (setF:) for the function transformer property (f).
#import "FunctionTransformer.h"
extern NSString* const kFunctionTransformer;
#interface FunctionTable : NSObject {
NSMutableArray *xs;
FunctionTransformer *f;
}
#property (nonatomic,retain) IBOutlet NSMutableArray *xs;
#property (nonatomic,retain) IBOutlet FunctionTransformer *f;
#end
// FunctionTable.m:
#import "LinearTransformer.h"
NSString* const kFunctionTransformer = #"Function Transformer";
#implementation FunctionTable
#synthesize xs, f;
-(id) init {
if ((self = [super init])) {
xs = [[NSMutableArray alloc] init];
self.f = [[LinearTransformer alloc] init];
[f release];
}
return self;
}
-(void)dealloc {
[f release];
[xs release];
[super dealloc];
}
-(void)setF:(FunctionTransformer *)func {
if (func != f) {
[f release];
f = [func retain];
[NSValueTransformer setValueTransformer:f forName:kFunctionTransformer];
}
}
#end
By default, FunctionTable uses a LinearTransformer. If you want to use a different one, simply set the FunctionTables's f property. You could do this in Interface Builder (IB) by using bindings. Note that in this simplistic implementation, the value transformer is always registered under the name "Function Transformer", effectively limiting you to one FunctionTable. A more complex scheme would be to give every FunctionTable their own function transformer name which would be used when registering their own FunctionTransformer.
To set everything up:
Open the app's main window nib in IB.
Instantiate an NSArrayController and a FunctionTable (and your custom app delegate, if any).
To the main window, add:
Buttons to add and remove elements,
labels and NSTextFields for the slope and intercept,
an NSTableView.
Set the table headers to "x" and "y" (not necessary for app to work)
Set up the connections:
Have the add & remove buttons send to the NSArrayController's add: and remove: actions.
Bind the NSTextFields values to the FunctionTables's f.slope and f.intercept key paths.
Bind the values of both columns of the NSTableView to FunctionTables's xs.
Set the value transformer for the second column to "Function Transformer"
Bind the NSArrayController's content array to the FunctionTable's xs key.
If you've got an app delegate, connect it to the File's Owner's delegate outlet.
Now build and run. You can use the add and remove buttons to add and remove rows to/from the table. You can edit the "x" and "y" column in a row (the latter is thanks to reverseTransformedValue:). You can sort by either the "x" or "y" columns. You can change the slope and intercept, though you won't notice the updates in the table unless you select the rows individually.
Advanced Topics
To fix the table view update problem, we need to propagate changes on one object's (a FunctionTransformer) properties to changes on another's (a FunctionTable) properties. We'll have the FunctionTable observe changes on its function transformer's properties and, when it FunctionTable receives a notice that any such property has changed, send a notice that the xs property has changed (which is a bit of an abuse, since xs hasn't actually changed). This is going to get a little magical, so bear with me.
An object subscribes to changes on another object using the KVO method addObserver:forKeyPath:options:context: of the other object, and unsubscribes using removeObserver:forKeyPath:. These methods just need to be called, not written. Notifications are handled by a observeValueForKeyPath:ofObject:change:context: method of the observing object, so this method needs to be written. Finally, an object can send its own notifications by calling willChangeValueForKey: and didChangeValueForKey:. Other methods exist to send notifications that only part of a collection has changed, but we won't use them here.
Our FunctionTable could handle the change subscription and unsubscription, but then it has to know which properties of the function transformer to observe, which means you couldn't change the type of the transformer. You could add methods to each concrete function transformer to subscribe and unsubscribe an observer:
#implementation LinearTransformer
...
-(void)addObserver:(NSObject *)observer
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
[self addObserver:observer
forKeyPath:#"slope"
options:options
context:context];
[self addObserver:observer
forKeyPath:#"intercept"
options:options
context:context];
}
-(void)removeObserver:(id)observer {
[self removeObserver:observer forKeyPath:#"slope"];
[self removeObserver:observer forKeyPath:#"intercept"];
}
#end
However, this will require a fair bit of code repetition in each method and across each concrete function transformer. Using some magic (reflection and closures, or as they're called in Objective-C, blocks ([2])), we can add the methods (named addObserver:options:context: and removeObserver:, as they are functionally similar to the KVO methods for subscribing & unsubscribing) to FunctionTransformer, or even to NSObject. Since observing all properties on an object isn't just limited to FunctionTransformers, we'll add the methods to NSObject. For this to work, you'll need either OS X 10.6 or PLBlocks and OS X 10.5.
Let's start from the top down, with the changes to FunctionTable. There's now new subtasks when setting the function transformer: unsubscribing from changes to the old transformer and subscribing to changes to the new one. The setF: method thus needs to be updated to make use of NSObject's new methods, which will be defined in a header named "NSObject_Properties.h". Note we don't need to worry about the implementation of these methods yet. We can use them here, having faith that we will write suitable implementations later. FunctionTable also needs a new method to handle change notifications (the observeValueForKeyPath:ofObject:change:context: referred to earlier).
#import "NSObject_Properties.h"
#interface FunctionTable
...
-(void)setF:(FunctionTransformer *)func {
if (func != f) {
[f removeObserver:self];
[f release];
f = [func retain];
[f addObserver:self
options:NSKeyValueObservingOptionPrior
context:NULL];
[NSValueTransformer setValueTransformer:f forName:kFunctionTransformer];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (object == f) {
if ([[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
[self willChangeValueForKey:#"xs"];
} else {
[self didChangeValueForKey:#"xs"];
}
}
}
Next, we write the new methods on NSObject. The methods to subscribe or unsubscribe from changes will loop over the object's properties, so we'll want a helper method, forEachProperty, to perform the loop. This helper method will take a block that it calls on each property. The subscription and unsubscription methods will simply call forEachProperty, passing a block that calls the standard KVO methods (addObserver:forKeyPath:options:context: and removeObserver:forKeyPath:) on each property to add or remove subscriptions.
//NSObject_Properties.h
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
#interface NSObject (Properties)
typedef void (^PropertyBlock)(objc_property_t prop, NSString *name);
-(void)forEachProperty:(PropertyBlock)block;
-(void)addObserver:(id)observer options:(NSKeyValueObservingOptions)options context:(void *)context;
-(void)removeObserver:(id)observer;
#end
// NSObject_Properties.m:
...
#implementation NSObject (Properties)
-(void)forEachProperty:(PropertyBlock)block {
unsigned int propCount, i;
objc_property_t * props = class_copyPropertyList([self class], &propCount);
NSString *name;
for (i=0; i < propCount; ++i) {
name = [[NSString alloc]
initWithCString:property_getName(props[i])
encoding:NSUTF8StringEncoding];
block(props[i], name);
[name release];
}
free(props);
}
-(void)addObserver:(NSObject *)observer
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
[self forEachProperty:^(objc_property_t prop, NSString *name) {
[self addObserver:observer
forKeyPath:name
options:options
context:context];
}];
}
-(void)removeObserver:(id)observer {
[self forEachProperty:^(objc_property_t prop, NSString *name) {
[self removeObserver:observer forKeyPath:name];
}];
}
#end

Resources