GNUstep Developer: Presenting Data in Table Views

Copyright (C) 2017 Graham Lee.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the page entitled “GNU Free Documentation License”.

1 About This Guide

You will learn how to use NSTableView to display tabular data. You should already be familiar with gorm and Project Center. You can either follow along, or clone the bitbucket project to see the finished project.

2 Introduction

Plenty of applications work on lists of objects, whether it’s finance software and a list of transactions, a photo viewer and a list of albums, or an email client and the list of messages.

It’s common to represent the collection of objects in a table view, with each row representing a different object and each column displaying a particular property of the objects in the list. GNUstep provides NSTableView as a reusable component for displaying tables in your apps. You adapt your list of objects onto the table view using an Objective-C protocol called NSTableViewDataSource.

3 Getting Started

Create a new Project Center application project called “Librarian”. You’ll need some data to show in the table, so make a new Objective-C class called Book. The interface file, Book.h, will declare the properties used to represent a book:

#import <Foundation/Foundation.h>  
 
@interface Book : NSObject  
{  
  NSString *title;  
  NSString *author;  
  NSNumber *pageCount;  
}  
 
@property (nonatomic, copy) NSString *title;  
@property (nonatomic, copy) NSString *author;  
@property (nonatomic, copy) NSNumber *pageCount;  
 
@end

and the implementation file, Book.m, implements those properties and manages memory.

#import "Book.h"  
 
@implementation Book  
 
@synthesize title;  
@synthesize author;  
@synthesize pageCount;  
 
- (void)dealloc {  
  [title release];  
  [author release];  
  [pageCount release];  
  [super dealloc];  
}  
@end

3.1 An aside on Objective-C properties

Depending on the compiler you use, you may not need to declare instance variables or explicitly synthesize Objective-C properties. I’m using GCC 7.1, and it is necessary there. If you’re unsure, consult your compiler manual.

4 Put your books on the shelf

A library isn’t much of a library with only one book, so create a new class, Shelf. A Shelf will hold multiple Books. The shelf will also implement the data source methods necessary to adapt to the table view, which are in the NSTableViewDataSource protocol.1 x

It won’t be much good having something that can show everyone its books if it doesn’t have any books, so you can additionally add an action method for creating a new book. Here’s the header, Shelf.h.

#import <AppKit/AppKit.h>  
 
@interface Shelf : NSObject <NSTableViewDataSource>  
{  
  NSMutableArray *books;  
  IBOutlet NSTableView *booksTable;  
}  
 
@property (nonatomic, readonly) NSMutableArray *books;  
 
- (IBAction)addBook:sender;  
 
@end

In Shelf.m, the first thing to do is to define the class and the management of the list of books.

#import "Shelf.h"  
#import "Book.h"  
 
@implementation Shelf  
 
- init  
{  
  self = [super init];  
  if (self) {  
    books = [NSMutableArray new];  
  }  
  return self;  
}  
 
- (void)dealloc  
{  
  [books release];  
  [super dealloc];  
}  
 
@end

4.1 Adding Books to a Shelf

Now you need to implement the action to add a book, and the table view data source methods. The action has two tasks: it adds a newly-created book to the list of books, and it tells the table view that the list has changed so that the book gets displayed. This method goes between the line @implementation Shelf and @end.

- (IBAction)addBook: sender  
{  
  Book *book = [Book new];  
  book.title = @"New book";  
  book.author = @"Unknown author";  
  book.pageCount = [NSNumber numberWithInteger:0];  
  [books addObject:book];  
  [book release];  
  [booksTable reloadData];  
}

4.2 Displaying Books in a table

As the table view data source methods are naturally grouped together, you can put them in a category on Shelf by creating a new @implementation block. The table view needs to know three things:

Therefore, there are three methods in the data source protocol.

The shelf’s table will have three columns, for a book’s title, author and page count. To simplify the logic for finding and updating values, in gorm you can give each table column an identifier corresponding to a property name on the Book object. This lets you use Key-Value Coding (kvc) to look up the properties based on the identifier. The data source methods then look like this:

@implementation Shelf (NSTableViewDataSource)  
 
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView  
{  
  return [books count];  
}  
 
- tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row  
{  
  id book = [books objectAtIndex:row];  
  return [book valueForKey:[column identifier]];  
}  
 
- (void)tableView: (NSTableView *)tableView setObjectValue:value forTableColumn: (NSTableColumn *)column row: (NSInteger)row  
{  
  id book = [books objectAtIndex:row];  
  [book setValue:value forKey:[column identifier]];  
}  
 
@end

4.3 Laying out the ui

Open the application’s interface in gorm, and add a button titled “Add Book” and a table view to the window. The interface will look something like figure 1.


PIC

Figure 1: The Library application window.


You need to tell gorm about the Shelf class so that you can integrate the controls with the Shelf’s behaviour. Switch to the Class browser in gorm, and from the Operations pull-down select “Load Class”. Navigate to Shelf.h and load that. Now Shelf appears in the class browser as a subclass of NSObject, as in figure 2.


PIC

Figure 2: The Shelf class is available in gorm.


With the Shelf class selected, select “Instantiate” from the Operations pull-down. An instance of Shelf is created and available from the Object Inspector (figure 3).


PIC

Figure 3: An instance of Shelf in gorm.


Inspect the instance of Shelf, and create a connection between its booksTable outlet and the table view, as in figure 4.


PIC

Figure 4: Connect the books table outlet to the table view.


Similarly, inspect the button and connect its target to the addBook: action on the Shelf instance.

Setting up the table view is quite involved. In the table view’s Attributes inspector, make sure that it has three columns. You can resize the columns to fit the width of the window and make sure the content of each column can be displayed in the available space. Select each column individually by clicking in its header cell at the top of the table, and change its title and identifier to match the property it will be displaying. Remember that the table view data source uses Key-Value Coding to match column identifiers to properties on Book instances, so be sure that the column identifiers and property names are spelled identically. For example, the attributes of the Title column are shown in figure 5.


PIC

Figure 5: Each table column’s title and identifier must be set.


Now go back to the table view and, in its Connections inspector, connect the dataSource outlet to the instance of Shelf you created earlier. Everything is set up, and when you build and run the application (figure 6) you can add books to the table.


PIC

Figure 6: The running application.


5 Formatting the Page Count

A problem will quickly become evident if you try using the library now: you can type anything into the Page Count column and the application will accept it, even if it isn’t a number. You need to restrict the value to only whole numbers. Moreover, you need to take the string that the user has entered and convert it into a number to store in the book’s page count, and convert that page count number back into a string for display in a table.

All of this conversion work can be done by a GNUstep class called NSFormatter. There is even a built-in subclass called NSNumberFormatter that does exactly what’s needed for the page count: only accept numbers, and convert between numbers and strings.

In principle it should be possible to use gorm to add a number formatter to the page count column of the table, but at time of writing that does not work in the current version of gorm. Instead, add a -awakeFromNib method to the Shelf class to add the formatter. As discussed in the Introduction to Project Center and gorm, -awakeFromNib messages are automatically sent to objects once the gorm interface has been loaded and all of the connections set up.

- (void)awakeFromNib  
{  
  NSTableColumn *pagesColumn = [booksTable tableColumnWithIdentifier:@"pageCount"];  
  NSCell *pagesCell = [pagesColumn dataCell];  
  NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];  
  [formatter setFormat:@"#,###"];  
  [formatter setAllowsFloats:NO];  
  [formatter setLocalizesFormat:YES];  
  [formatter setMinimum:0];  
  [pagesCell setFormatter:formatter];  
}

Re-build and re-launch the app, to verify that now only numbers may be added to the page count.

6 Summary

You have built an application that succinctly displays a collection of objects in a table using GNUstep’s NSTableView class. You have also used NSFormatter to control the acceptable values in a user interface and to format the model data for display.

7 Resources

The source code for the Librarian application created here is available in its entirety from gs-librarian on Bitbucket, under the terms of the GNU General Public License, Version 3.