Search code examples
cocoacocoa-bindingsnsarraycontroller

Binding NSArrayController's content array to a custom (non-array) object


Is there a way to bind an NSArrayController's content array to a custom object that is not technically an array, but behaves like one?

Specifically, I'm trying to bind an NSTableView to a PDFDocument (so, one row in the table per PDFPage of the document). PDFDocuments are not arrays, but have many of the same types of methods (such as insertPage:atIndex:, removePageAtIndex:, etc.).

I would ultimately like to just write a wrapper that translates the PDFDocument's methods to NSMutableArray's methods, but am not sure what approach to take. I've made a few attempts, and the bindings just don't seem to work properly.

Or, more specifically, any way to bind a table to the pages of a PDFDocument to mimic a PDFThumbnailView (for those of you who know PDFKit)?

Thanks!


Solution

  • Bindings are built on Key-Value Observing and Key-Value Coding.

    To merely query the PDFDocument for its pages, the array controller and bindings will use -valueForKey:. As documented in Key-Value Coding Programming Guide: Accessor Search Implementation Details, that will search for a simple getter using a variety of naming patterns. Since there's no simple getter for a "pages" property, it will search for the indexed collection accessors. In particular, it will look for -countOfPages and one of -objectInPagesAtIndex: or -pagesAtIndexes:. None of those exist on PDFDocument, but very similar methods exist. You can implement these in terms of the available methods using a category.

    However, if the PDFDocument is going to be mutating its list of pages and you want the array controller to automatically pick up such changes, you have a problem. A property has to be modified in a KVO-compliant manner for bindings to pick up the change. If the insertion routine were named -insertObject:inPagesAtIndex:, that would be KVO-compliant, but it's not. It's named -insertPage:atIndex:, which KVO won't recognize. This is not something you can fix with a category because what's important is what method gets used, not what method is merely available.

    You could try to use a subclass of PDFDocument that overrides -insertPage:atIndex: to call -willChange:valuesAtIndexes:forKey: and -didChange:valuesAtIndexes:forKey: before and after calling through to super. However, it's not guaranteed that all insertions even go through the public method. It's possible the class could sometimes directly modify its internal state without calling any method that a subclass could properly override. (All of the same applies to removal methods.)

    If you're sure that all mutations are initiated by your code, you can make your code use the KVO-compliant methods, which you'll have to implement in a category or subclass.