0
votes

I have a small OS X test project written in Objective-C using storyboards with XCode 6.2, the goal of which is to test the binding of an NSArray to a View-based NSTableView. Although this area has been beaten to death online, I cannot for some reason make my binding work.

I have defined a simple object TESTPerson which has been sub-classed from NSObject as follows:

//  TESTPerson.h
//  TBLViewBindingTest

#import <Foundation/Foundation.h>

@interface TESTPerson : NSObject

@property (nonatomic,strong) NSString *strFirstName;
@property (nonatomic,strong) NSString *strLastName;

@end


#import "TESTPerson.h"

@implementation TESTPerson

-(instancetype)init
{
  if(!self)
  {
    self = [super init];
  }
  _strFirstName = @"First Name Test";
  _strLastName = @"Last Name Test";
  return self;
}
@end

I dropped a NSTableView into the view provided by the Storyboard, then dropped a NSArrayController into the View Controller scene.

I create and attach IBOutlets for the NSTableView and NSArrayController instances in ViewController.h. NSArray *arrayNames is also declared and will be used as the content source by the NSArrayController.

//  ViewController.h
//  TBLViewBindingTest

#import <Cocoa/Cocoa.h>
#import "TESTPerson.h"

@interface ViewController : NSViewController

@property (strong) IBOutlet NSTableView *myTableView;
@property (nonatomic,strong) NSArray *arrayNames;
@property (strong) IBOutlet NSArrayController *myArrayController;

@end 


viewDidLoad in ViewController.m is updated to create two instances of TESTPerson, which are then added to the _arrayNames property.

//  ViewController.m
//  TBLViewBindingTest

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];
  TESTPerson *p = [[TESTPerson alloc]init];
  TESTPerson *p1 = [[TESTPerson alloc]init];
  _arrayNames = [[NSArray alloc]initWithObjects:p,p1, nil];
}

- (void)setRepresentedObject:(id)representedObject
{
  [super setRepresentedObject:representedObject];
}
@end


I selected the ArrayController in the Storyboard and set the Class Name in the Attributes tab to 'TESTPerson', and set the Bindings tab to bind the ArrayController with my ViewController using a Model Key Path of arrayNames. It is my expectation that arrayNames will be populated with the two TESTPerson instances created in the ViewController viewDidLoad method.

I then selected the Table View in the Storyboard and set its Table Content to bind to the Array Controller using a Controller Key of arrangedObjects.


Drilling down into the TableView hierarchy I selected the Table View Cell node for each column and bound the Value to Table Cell View with Model Key Paths of objectValue.strFirstName and objectValue.strLastName respectively.

My understanding is this is all that is required in order to present data from my array in the tableview. When I execute the program however, no table rows are displayed. I did a dump of the ArrayController content via NSLog by adding a button / method:

NSLog(@"%@",[_myArrayController content]);

shows me that _myArrayController does not have any content:

`2015-03-22 13:48:49.379 TBLViewBindingTest[48776:4766635] (
)`

I double-checked that the NSArrayController in the View Controller Scene is bound to IBOutlet _myArrayController (it was).

I then added a line of code in viewDidLoad to see if I could force something into the _myArrayController:

[_myArrayController addObjects:_arrayNames];
NSLog(@"%@",[_myArrayController content]);

results in a populated tableView on the screen and objects in _myArrayController:

2015-03-22 13:50:28.240 TBLViewBindingTest[48807:4768827] (
    "<TESTPerson: 0x610000024ec0>",
    "<TESTPerson: 0x610000024ee0>",
    "<TESTPerson: 0x610000024ec0>",
    "<TESTPerson: 0x610000024ee0>"
)

To me it appears that the binding between the ArrayController instance and _arrayNames is not accepted. I have tried many things - adding the TESTPerson properties as keys in the ArrayController Attributes tab etc, but to no avail. Using NSObject as the base for TESTPerson should be enough to make the TESTPerson class KVC compliant(?), but I am wondering if the ArrayController does not agree?

As far as I can tell, everything that needs to be in place for basic data display is there. What have I missed?

1

1 Answers

2
votes

The problem was that setting the content of _arrayNames in -viewDidLoad is too late for the binding execution between the NSArrayController and the Controller Content of the targetViewController. (-viewWillAppear is too late as well)

What to do? To solve the problem in the sample code of this question, you can create a new designated initializer for the viewcontroller and set the array content there.

Segues in OS X 10.10 are another animal though. The designated initializer for NSViewController as called in a segue seems to be -initWithCoder. If you override -initWithCoder and make a call to [super initWithCoder:] from the override, you will see that self.representedObject == nil in the target viewController.

Typically I would pass an NSDictionary as a representedObject.

So. Filling a source array that has been defined as a property on the viewController for the binding between a NSArrayController and the Controller Content of the viewController (Bind to myViewController with Model Key Path == self.myArray) in any of the -viewWill*** methods is too late.

self.representedObject is nil in the designated initializer for the target viewController in a segue, meaning that you can't fill self.myArray there.

For kicks, I passed a NSArray directly in self.representedObject for the target viewController in a segue. I then adjusted the Controller Content binding between the NSArrayController and the targetViewController so the Model Key Path was self.representedObject. If you do this the binding works.