Basic methods

@interface PatientList : NSViewController {
   NSMutableArray *patientArray;     // this array will genuinely contain the data to display
   IBOutlet NSTableView *tableView;  // this array is a link for IB
}

The 2 following methods must be implemented :

- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
   return [patientArray count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn
            row:(int)rowIndex
{
   NSString *identifier = [aTableColumn identifier];  // get the column identifier
   Patient *unPatient = [patientArray objectAtIndex:rowIndex];
   return [unPatient valueForKey:identifier];
}

Optionally, a sort method can be implemented. Under the IB inspector, Table column Attribute needs “sortKey” and “Selector” to be set. sortKey is usually equal to the col identifier. Selector can be compare: or localizedStandardCompare:. For integer values in an NSTableView, use localizedStandardCompare:. For NSString, use compare:.

- (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
   NSArray *newDescriptors = [aTableView sortDescriptors];
   [patientArray sortUsingDescriptors:newDescriptors];
   [tableView reloadData];
}

IB connections

  1. File’s Owner Class Id must be set to PatientList
  2. connect ‘File’s Owner’ tableView outlet to the NSTableView graphic
  3. connect _NSTableView_ graphic -> datasource to ‘File’s Owner’

Note : if you do not see datasource in the inspector, it’s certainly because you have selected the NSScrollView instead. The NSTableView is inside the NSScrollView. The column identifier is used to be able to give the right value from patientArray. This must match the field name of Patient Class. From IB, select an entire column and set identifiers in a simple way, something as follows :

Title and Identifier relationship
Title Identifier
Name name
FirstName firstname
Nb Consults nbConsults

Capture row selection change

This needs a delegate link from NSTableView IB to “File’s Owner”

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
   int row = [tableView selectedRow];
   if (row == -1) return;
   Patient* aPatient = [patientArray objectAtIndex:row];
   NSInteger idp = aPatient.idPatient;
   DLog(@"%d",idp);
}

Reselect the same row after [tableView reloadata]

NSInteger row = [self.tableView selectedRow];
[self.tableView reloadData];
[self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];

Deselect all rows

[patientTableView deselectAll:self];

Retrieve information from multiple selected rows

We need to use the following method :

- (NSIndexSet *)selectedRowIndexes // Returns an index set containing the indexes of the selected rows.

Let’s say multiple lines have been selected from an NSTableView : patientTableView. A button has been pushed and the action is addMember:

- (IBAction)addMember:(id)sender
{
   NSIndexSet *allrows = [patientTableView selectedRowIndexes];
   if ([allrows count] == 0) { return; }

   // Parse the different rows as follows
   NSUInteger row = [allrows firstIndex];
   while (row != NSNotFound)
   {
      // use the currentIndex
      aPatient = [patientArray objectAtIndex:row];

      // Do whatever you want with the aPatient instance, here we update a database
      [self mysqlAddPatientToGroup:aPatient gid:10];

      // step to the next row
      row = [allrows indexGreaterThanIndex:row];
   }
}

Multiple NSTableView in an NSView

We only have some general delegate methods to deal with the different NSTableView(s).
So we need to add some extra code to check with which NStableView instance the delegate method is dealing with.
For details, see http://stackoverflow.com/questions/12250028/cocoa-multiple-view-based-nstableviews

@interface Comptes : NSViewController {
   NSMutableArray *consultArray;
   NSMutableArray *chargeArray;
   IBOutlet NSTableView *consultTableView;
   IBOutlet NSTableView *chargeTableView;
}
// **************************************************************
// NSTableView functions
//
// ==============================================================
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
   if (aTableView == consultTableView ) {
      return [consultArray count];
   }
   if (aTableView == chargeTableView ) {
      return [chargeArray count];
   }
   return 0;
}

// We use the column identifier to be able to give the right value from patientArray.
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn
            row:(int)rowIndex
{
   ConsultExtent *aConsult;
   Charge *aCharge;

   NSString *identifier = [aTableColumn identifier];  // get the column identifier 

   if (aTableView == consultTableView ) {
      aConsult = [consultArray objectAtIndex:rowIndex];
      return [aConsult valueForKey:identifier];
   }
   if (aTableView == chargeTableView) {
      aCharge = [chargeArray objectAtIndex:rowIndex];
      return [aCharge valueForKey:identifier];
   }

   // some extra checks
   assert(aTableView == consultTableView || aTableView == chargeTableView );
   return 0;
}

// column sort
- (void)tableView:(NSTableView *)aTableView sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
   NSArray *newDescriptors = [aTableView sortDescriptors];

   if (aTableView == consultTableView ) {
      [consultArray sortUsingDescriptors:newDescriptors];
      [consultTableView reloadData];
      return;
   }
   if (aTableView == chargeTableView ) {
      [chargeArray sortUsingDescriptors:newDescriptors];
      [chargeTableView reloadData];
      return;
   }
   assert(aTableView == consultTableView || aTableView == chargeTableView );
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
   NSTableView *aTableView = [notification object];

   if (aTableView == consultTableView ) {
      NSInteger row = [consultTableView selectedRow];
      if (row == -1) { return; }
      return;
   }
   if (aTableView == chargeTableView ) {
      NSInteger row = [chargeTableView selectedRow];
      if (row == -1) { return; }
         return;
   }
   assert(aTableView == consultTableView || aTableView == chargeTableView );
}

Cells edition

From the Interface Builder window

  • select the NSTableView Oject
  • Right click -> Delegate -> File’s Owner
  • Add this method in the code :
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject
   forTableColumn:(NSTableColumn *)aTableColumn
              row:(NSInteger)rowIndex
{
   ConsultExtent *uneConsult;

   // Check the NSTableView instance in which the cell edition has been triggered
   if (aTableView == consultTableView ) {
      // Check the column identifier
      if ([[aTableColumn identifier] isEqualToString:@"invoice"]) {
         // Assign the typed value to the consult instance
         aConsult = [consultArray objectAtIndex:rowIndex];
         aConsult.paiementEncaisse = [NSString stringWithFormat:@"%@",anObject];
         // modify the NSMutableArray
         [consultArray replaceObjectAtIndex:rowIndex withObject:aConsult];
         [consultTableView reloadData];
      }
   }
   // some assertions, to make sure we are not making bad things
   assert(aTableView == consultTableView || aTableView == chargeTableView );
}

Deal with NSTableView cell edition aborted by another event

This is really something to be dealt precociously. If an NSButton is pressed while an NSTableView cell is being edited, this will make empty cells displayed and/or some rows not in sync with what should be displayed. I did spend some time trying to figure this out. The most elegant solution is to make sure the NSTextFieldCell is not the firstResponder anymore. Actually, we force the end of the cell edition so that we get something clean. In each IBAction method, we have to call the makeFirstResponder: method by giving any Graphical object outside the selected NSTableView. Let’s say we have an NSDatePicker object.

   program.h
   IBOutlet NSDatePicker        *startDate;

The following action method for an NSButton pressure will look like :

- (IBAction)currYear:(id)sender
{
   // Forces the end of the cell edition so that we get something clean.
   [[startDate window] makeFirstResponder:startDate];
}

How to edit an NSTableView cell with a single-click

As I have posted onto stackoverflow, I think the most elegant option is to subclass NSTableView as follows :

#import <Cocoa/Cocoa.h>
@interface myNSTableView : NSTableView {
}
- (void)singleClickEdit: (id)sender;
@end
#import "myNSTableView.h"
@implementation myNSTableView
- (void)awakeFromNib
{
   [self setAction:@selector(singleClickEdit:)];
}

- (void)singleClickEdit: (id)sender
{
   NSLog(@"singleClickEdit:");
   [self editColumn:[self clickedColumn] row:[self clickedRow] withEvent:nil select:NO];
}

@end

Instead of NSTableView, use the myNSTableview class, ie :

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

@interface Preference : NSPanel {
   NSMutableArray             *chargeArray;
   IBOutlet    myNSTableView  *chargeTableView;
}

From IB, select the NSTableView graphical object which has been put in the NSView. Select the “Indentity Inspector” Tab, and select myNSTableView instead of NSTableView.
You are done.

Change the background color of a selected row

We just have to subclass NSTableRowView. It has methods for drawing the background for selected and deselected rows. To get the NSTableView instance to use your subclass, just implement the tableview delegate method : tableView:rowViewForRow:, and return an instance of the NSTableRowView subclass.

   #import <Cocoa/Cocoa.h>
   @interface MyNSTableRowView : NSTableRowView
   @end

 

   #import "MyNSTableRowView.h"

   @implementation MyNSTableRowView
   - (id)init
   {
      if (!(self = [super init])) return nil;
      return self;
   }

   - (void)drawSelectionInRect:(NSRect)dirtyRect {
     if (self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) {
        NSRect selectionRect = NSInsetRect(self.bounds, 2.5, 2.5);
        [[NSColor colorWithCalibratedWhite:.65 alpha:1.0] setStroke];
        [[NSColor colorWithCalibratedWhite:.82 alpha:1.0] setFill];
        NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRoundedRect:selectionRect
                                                                      xRadius:6 yRadius:6];
        [selectionPath fill];
        [selectionPath stroke];
     }
   }
   @end

From the main delegate class, add this :

   - (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
   {
      DLog(@"row =%ld",row);
      MyNSTableRowView *rowView = [[MyNSTableRowView alloc]init];
      return rowView;
   }