TweetFollow Us on Twitter

My First Yellow Box App

Volume Number: 13 (1997)
Issue Number: 9
Column Tag: develop

My First Yellow Application

by Éric Simenel

Since most of us are in the process of exploring the OpenStep (Yellow Box) framework and its development environment, it's likely that we are going to encounter the similar pitfalls. I thought it would be useful to describe my first experience at writing something more ambitious than a simple snippet, so that you could benefit from my trials. To make the most out of this article, you'll need to already have some knowledge of Yellow Box. This article is not a tutorial, it's more like a journal...

First, let me give you a little background. I've been programming for the past 17 years (13 on Macintosh) using a lot of languages (in order of appearance: FORTRAN, 6502 asm, BASIC, COBOL, LISP, Pascal and Object Pascal, 68K asm, C and C++, PowerPC asm and Java), and a lot of environments and Operating Systems (micros, minis and mainframes).

As far as frameworks go, I've only used a very little bit of MacApp and more of ODF (the late OpenDoc Development Framework), but mainly I've used the Mac Toolbox when developing company and personal stuff. The main reason is that I've been developing with it for so long that it's just like an old friend; you know what you can ask it to do and what you'd better not.

Since I've been doing Object Oriented Programming for the last 6 years, I've always regretted a little bit that the Mac Toolbox doesn't have OO APIs. Of course, I could have used a framework but this adds an extra layer which you sometimes appreciate and sometimes don't.

Well, with Rhapsody, it's OOP from the word go, so no more excuses.

At first, the Objective-C syntax with all of its "["s and "]"s looks really different, but I'd say that after a day or two, you don't even think about it anymore. And anyhow, the Developer Release or the Premier Release will come with the Objective-C modern syntax which will be more familiar to the C/C++ developer.

Although a lot of people admire Interface Builder (IB), and it's indeed a very nice tool, I, for one, think that the real power comes from the Yellow Box framework. When I developed my first Yellow application, I spent maybe 5-10% of my time in IB and the rest in Project Builder (PB). I must add that if IB didn't exist, we would need to spend an incredible amount of time writing source code to do what it does since this tool is much more than a simple graphic user interface designer tool, but more on that below...

While writing my sample code, I learned quite a few things which I didn't find explicitly in the documentation, so I thought it might be useful to share them. I must add that I later found some (but not all) of this information in documents which I didn't possess at first, and since I believe this will be the case for most of you, and that, anyway, who reads the entire documentation before starting coding, it's still useful to have those points collected in a useful way, i.e. this article.

I guess readers of the late develop magazine will remember that I collect Comics Books, Comics in short, and my non-trivial sample code is the port of an existing Comics Database Management Macintosh application to the Yellow Box. There is an additional document "The way I collect Comics" in the development folder, which explains why I designed the user interface the way it is. This article deals with the "how" I designed it.

The development folder containing all the source code, interface files, project files, etc. is on the Rhapsody Developer Release CD, and will also be on our web site, whose address is not known at this date but which I trust you shouldn't have too much difficulty to find.

This article contains extracts of the source code only when relevant, you might find it useful to read the entire source code along with this article.

What's in a Nib (NeXT Interface Builder) File

Everything which has been created in the nib while using IB (i.e., every icon which you see in the "Instances" panel), will be instantiated when the nib opens.

That means that when your application launches, its main nib is opened, all objects which have an icon in the "Instances" panel will be instantiated, then their init method will be called, then the outlet connections you have established with IB will be filled with the appropriate value, and then their awakeFromNib method will be called.

As an example, since my Yellow application deals with one unique Comics database object, instead of allocating it in the source code somewhere, I just instantiated it in the nib file. The nib file is not just for graphic user interface elements.

This is also one of the reasons why you should use subprojects with their own nib files. You load these nib files with the loadNibNamed: method (from NSBundle) when needed which saves on memory usage and improves performance. And speaking of memory...

Memory Problems

Always when, and only when, you explicitly call the alloc class method, or the retain, copy or mutableCopy instance methods, you must release or autorelease the object thus allocated. All other objects you get through another method, you neither release nor autorelease .

That means that if you write

NSAClass *obj = [[NSAClass alloc] init];

then you must also do either [obj release] if the method where you allocated it is the only one using it, or [obj autorelease] if the method is returning this object to somebody else. But if you write, for example.

NSFileManager *nsfm = [NSFileManager defaultManager];
NSData *data = [nsfm contents AtPath:fileName];

then don't write [nsfm release]; nor [data release]; or you'll end up in trouble (I did).

Also, if you write

NSString *str = [[NSString alloc] initWithCString:myCString];

then you must write [str release]; or [str autorelease]; depending on the usage, but if you write

NSString *str = [NSString stringWithCString:myCString];

then don't release str, (in this case it has already been autoreleased by the class method stringWithCString:).

Memory Protection

Although when you mismanage memory in the Yellow Box, it's not as bad (directly) as on the Mac OS, you may still have some surprises. These problems come from releasing an object too many times (see above), going outside the range of an NSArray object, and certainly other ways which haven't bitten me yet. In all those cases of memory mismanagement, if you launched your application from the launcher/debugger window in PB, you'll get explicit error or exception messages. You then fix the problem in your source code, build and test again. Afterwards, in some cases, although everything looks fine, you'll experience minor (but irritating) annoyances: PB won't build anymore (the compiler keeps sending obscure messages), PB won't index your project(s) anymore, IB won't save your modifications, or PB or IB will quit suddenly and then refuse to launch again.

Depending on the exact symptom, I found the following solutions: quit PB and IB, relaunch them, everything's fine (this works 50% of the time), if not, log out, log in again (takes just a couple of seconds), relaunch them, everything's fine (this works 45% of the time), if not, power off, then cold start again (this works 5% of the time). In any case, I never lost any data.

On a brighter side, the more I developed with this framework, the less I wrote these silly memory mismanagement mistakes, meaning that I have less and less trouble with the tools themselves. And anyway, those bugs are currently being tracked and will be fixed.

More about memory

All objects in the Yellow Box are refcounted. The alloc class method allocates the object and sets its refcount to 1. The retain instance method increments the refcount, the release instance method decrements the refcount and if it becomes 0, deallocates the object. The autorelease instance method places the object in an autorelease pool. When the current event ends, or in other words, when the control returns to the Main Event Loop, all objects in this pool will be sent the release message once, and if their refcount becomes 0, they are deallocated. The copy instance method will do the same thing as the retain method if the object is immutable. If the object is mutable, copy will create a new object with the same content and a brand new refcount of 1, just as mutableCopy does.

You should never call the dealloc method yourself.

Whoever creates an object or increments its refcount, by any of the provided means, is responsible for decrementing its refcount by calling release or autorelease . That's the reason why autorelease is provided. For example, when you create and return an object to your caller, the caller has no idea how you created the object; it could be an allocation, but it could also be a static or on a stack or whatever. Therefore the caller must not release this object, you have to. But your problem is that you may no longer have access to this object later when the moment comes. What you do, in this case, is to autorelease the object before you return it. If the caller wishes to keep it, then the caller must do a retain on it, but if the caller doesn't care, then the object will be automatically released at the end of the current event since nobody retained it.

Updating Window Content

The updating mechanism is very different from what you are used to on Mac OS. By default your window is buffered (as in offscreen), it can also be retained and non-retained, but I must say I was rather surprised by the standard graphic objects reaction to those choices, try it and you'll see what I mean. It looks like some of these objects assume they are living in a buffered window and have their own bit cache management relying on that assumption, and this goes awry whenever the window is not buffered. All the standard graphic objects call you (as a developer) to be able to redraw, such as drawRect: (for an NSView) or they will ask for some data as in willDisplayCell:atRow:column: (for NSBrowser) and then draw.

So far, so good; it's not really different from the Mac OS frameworks we know. The difference comes from the fact that you will not be called at all most of the time. If your window was in the background and comes to the foreground, chances are that the update will occur from the offscreen buffer and your redraw method won't be called at all (so if you put in some debugging display like I did at first, don't be surprised if you don't see it).

Some Slightly Annoying Surprises

Although the APIs of NSBrowser and NSTableView are very similar, their mechanism is quite different. To use NSTableView, you must (among other things) implement the numberOfRowsInTableView: method and, as its name suggests, you must return the number of rows of the table. To use NSBrowser, you must (among other things) implement the numberOfRowsInColumn: method. The difference is that numberOfRowsInTableView: is called a zillion times by the NSTableView object, whereas numberOfRowsInColumn: is called only once by the NSBrowser object whenever you click on a cell in a column which will make the browser fill a new column to its right.

So your strategy as a developer must be quite different. In a browser, it is the right thing to do the computation of the numbers of rows of the column you're being asked for in numberOfRowsInColumn:, whereas, in a table, you'd better do the intensive computation only at awakeFromNib (or init time, depending on your situation) time, and then only if the number of rows changes, save off this number of rows in a field of your controller so that you can just return this value when asked in numberOfRowsInTableView:.

Setting up IB and PB

The default rules for indenting text in the PB text editor are not necessarily what you'd wish they are. Don't hesitate to modify them in the "Preferences" window till the indentation is as you like. You'll need to set up the "Indentation" panel criteria and the "Key bindings" panel criteria.

If you happen to close a Palette, using the "Close Palette" menu item of the "Palettes" menu in IB, this action won't close the Palettes window, but remove the current selected palette icon from this window. To reinstall it, use the "Preferences" window of IB.

GDB

Since it is rather unlikely that we write perfect source code the first time, it is useful to follow what's happening in the debugger, or at least in the console. That's where gdb (gnu debugger) makes its entrance.

What happens to me the most, and following the works of my colleagues, I found that I am not the only one, is that I design my interface with IB, write my source code with PB, build and test and nothing happens. The stuff I just wrote just does not do what it is supposed to. In nearly all these cases, what's wrong is that I had forgotten to establish crucial connections with IB (such as a delegate or a datasource, or even an action method). Most likely you will experience the same problem.

So the first step is a double one: verify your connections again with IB and add some tracing code using NSLog (and don't forget to launch your application through the launcher/debugger window of PB, instead of launching it from the Workspace; if you do, however, you can still see your NSLog messages in the main console window which you can display from the "Tools" menu of the Workspace).

The prototype of NSLog is

(void)NSLog(format, ...);

and you use it as you would use printf, except that NSLog has an extra format "%@", which enables the printing of the description of an object (very useful).

If you end up being halted in gdb because of an exception or an error, the following useful commands (also accessible from the graphic user interface of the launcher/debugger window) will help you determine the cause of the problem. There are many more commands of course, so read the gdb documentation or use its help command to find out more.

Table 1. Useful commands in gdb

Command Description
po <object> prints the description of an object
print * <object> prints the content of an object
print * <structure> prints the content of a structure
print expression prints the evaluated expression
bt prints the stack crawl
future-break <method> sets a breakpoint for code not loaded yet
kill terminates the process being debugged

Note: You can also evaluate and call methods from known objects such as

print * [comicsBase titles] or
print * (NSScroller *)[self verticalScroller]

Last Note

Before we enter the detail of each class in my first Yellow application, I must say that I was pleasantly surprised by the speed of my development. In less than 3 weeks (and that's my first Yellow application, meaning that I spent a lot of time in the documentation trying to understand how everything works), I reached a level (converting data from the Mac OS database, archiving and unarchiving data the Yellow Box way, displaying the database content in 4 different ways, and modifying the data in a rather complex user interface) which had taken me 2 or 3 months to reach in C++ with the Mac Toolbox on Mac OS (remember, this is a port from a Mac OS application which I've been using for years). So, whenever you hear some guy speaking about Rapid Application Development (RAD) regarding the Yellow Box framework, that's the real untarnished truth, not just some sales talk.

One last thing before we go, the Yellow Box framework, like MacApp, ODF and certainly others, is a Model-View-Controller kind of a framework. Very classic these days, very powerful, and very helpful for object reuse. When I talk later about a View (i.e. user interface), I mean it in the Model-View-Controller context, so don't confuse it with an NSView which is a specific class of the Yellow Box.

Onward with the Details

ComicsObj

This is the Model part of my application. It only deals with data, not user interface.

The CComics class contains mainly an NSMutableArray of CTitles objects which contains mainly an NSMutableArray of CIssues objects. The important methods are initWithCoder: and encodeWithCoder: which are implemented in all 3 classes to allow the archiving and unarchiving of the database (look into the source code to see their implementation, nothing really difficult), sortArray:withBrand:withSeries:withKind:withState:withSort: (in CComics) which not only sorts but also extracts the appropriate titles depending on the criteria parameters, and which will be called from nearly all the controller objects of the user interface, and various accessor methods for each class.

Since the comics database will be accessed from nearly all the controller objects, it was expedient to set up a global object (of class CComics) named comicsBase declared in ComicsObj.h. It could also have been an outlet, paired with an accessor, in the main controller (see the above paragraph about nib files), and then accessed from everywhere with [[NSApp delegate] comicsBase]. This is mainly a style choice from the developer.

Since the comics database already existed on Mac OS and I was reluctant to reenter all the data (see the "The way I collect Comics" document for further explanation), the init method of CComics can either convert the Mac OS database or load the Yellow Box database thus:

#if realLoad
  self = [NSUnarchiver unarchiveObjectWithFile:fileName];
  [self retain];
  comicsBase = self;
#else
  comicsBase = self;
  [self setTitles:[NSMutableArray array]];
  [self _convertFromMac];
  [self save:nil];
#endif
return self;

You'll note that in the first case, comicsBase must be assigned only after the unarchiving (self is changed), whereas in the other, it must be assigned before the call to _convertFromMac which uses this global object.

Since this conversion only has to occur once, unless I destroy the application or have data corruption, I do a special build setting realLoad to 0 to convert the database. Afterwards, realLoad is set back to 1.

VerifyController (the main controller)

The Controller for the "Verify" window which is a View.

This is a very basic use of an NSTableView. Just don't forget, in IB, to connect both the dataSource and delegate outlets of the NSTableView object to this controller and implement the 2 minimum requested methods numberOfRowsInTableView: and objectValueForTableColumn:row: and your table will be fine.

To be able to return appropriate content in objectValueForTableColumn:row:, you should also not forget to give an identifier to each column in the IB inspector window; this identifier may or may not be the same as the column title (your choice, in most cases, it's a good idea to give them the same name, it makes source code writing easier later).

Since this is the main controller, it also provides the appropriate action methods (to be connected with the appropriate menu items in IB) to deal with the objects from the subprojects (browser, calendar and title longevity). Furthermore, as an NSApp delegate (don't forget to establish the connection in IB), it also implements the applicationShouldTerminate: action method which, in this case, will lead to the saving of the comics database if it has been modified.

Each time the user chooses a new choice in the popup buttons, the selChanged method is called, which returns a new array of the appropriate CTitle objects, saves the new number of rows (see the above paragraph about the number of rows in an NSTableView), and calls the private method _update which, by calling the noteNumberOfRowsChanged and reloadData, will force a redisplay of the table.

Listing 1. Extract from VerifyController.m

- (void)selChanged
{
  [comicsBase sortArray:array withBrand:brand 
   withSeries:series withKind:kind withState:state 
   withSort:sort];
  nbRows = [array count];
  [nbSelTitles setIntValue:nbRows];
  [self _update];
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
  return nbRows;
}
- (id)tableView:(NSTableView *)tv objectValueForTableColumn:
(NSTableColumn *)tableColumn row:(int)row
{
  CTitle *thisTitle = [array objectAtIndex:row];
  NSString *identifier = [tableColumn identifier];
  if ([identifier isEqualToString:@"Abb"])
    return [thisTitle abb];
  else if ([identifier isEqualToString:@"Title"])
    return [thisTitle title];
  else return [thisTitle listIssues];
}
- (void)_update
{
  [verifyView noteNumberOfRowsChanged];
  [verifyView reloadData];
}

BrowserController

The Controller for the "Browser" window which is a View.

This is a very basic use of an NSBrowser. Just don't forget, in IB, to connect the delegate outlet of the NSBrowser object to this controller (the File's owner icon should be of custom class BrowserController since this is a subproject) and implement the 2 minimum requested methods numberOfRowsInColumn and willDisplayCell:atRow:column:, and your browser will be fine. Also remember to call [[browser window] makeKeyAndOrderFront:nil]; in the awakeFromNib method or else you won't see any window and lose some time understanding why.

Since the browser is defined in a subproject of the application project, we have to load its nib file, when the user wants to see this window. The menu item he will use is connected to the

- (void)newBrowser:(id)sender { [[BrowserController alloc] init]; }

action method in the VerifyController which is the main controller. The init method of BrowserController will do the nib loading (among other things):

Listing 2. Extract from BrowserController.m

- (id)init {
  if (self = [super init])
  {
    // the browser lies in a subproject, so let's get its nib
    if (![NSBundle loadNibNamed:@"Browser" 
    owner:self])
    {
      NSLog(@"Unable to load Browser.nib"); 
      [self release];
      return nil;
    }
    array = [[NSMutableArray alloc] 
            initWithCapacity:1500];
  }
  return self;
}
- (int)browser:(NSBrowser *)sender numberOfRowsInColumn:(int)column
{
  short brand, series, state, kind;
  // the first 4 colummns have all only 3 cells
  if (column < 4) return 3;
  // if not, let's see what the user has currently selected in the first colums
  brand = [sender selectedRowInColumn:0];
  series = [sender selectedRowInColumn:1];
  state = [sender selectedRowInColumn:2];
  kind = [sender selectedRowInColumn:3];
  // so that we can retrieve the appropriate titles (we don't care for 
  // sorting in this browser)
  [comicsBase sortArray:array withBrand:brand 
    withSeries:series withKind:kind withState:state  
    withSort:0];
  return [array count];
}

- (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(int)row column:(int)column
{
  switch(column)
  {
    case 0: switch(row)
    {
      case 0: [cell setStringValue:@"All Brands"]; break;
      case 1: [cell setStringValue:@"Marvel"]; break;
      case 2: [cell setStringValue:@"DC & Others"]; 
              break;
    } break;
    case 1: switch(row)
    {
      case 0: [cell setStringValue:@"All Series"]; break;
      case 1: [cell setStringValue:@"Long"]; break;
      case 2: [cell setStringValue:@"Mini"]; break;
    } break;
    case 2: switch(row)
    {
      case 0: [cell setStringValue:@"All States"]; break;
      case 1: [cell setStringValue:@"Dead"]; break;
      case 2: [cell setStringValue:@"Live"]; break;
    } break;
    case 3: switch(row)
    {
      case 0: [cell setStringValue:@"All Kinds"]; break;
      case 1: [cell setStringValue:@"Main"]; break;
      case 2: [cell setStringValue:@"Dual"]; break;
    } break;
    case 4: [cell setStringValue:
              [[array objectAtIndex:row] title]];
            break;
  }
  // the 5th column is the last
  [cell setLeaf:(column == 4)];
  // this means this cell is ready for display
  [cell setLoaded: YES];
}

TitleLongevityController and TitleLongevityView

The Controller for the "Title Longevity" window which is a View, and the custom view inheriting from NSView which implements the graph.

This is a very basic use of an NSView (NSCustomView in IB). The TitleLongevityView is just a graph, and the controller allows the user to change the criteria of the graph (through the popup menu buttons). TitleLongevityView has to implement just 2 methods (initWithFrame: and drawRect:) to work fine. Each time a criteria changes, the display (of TitleLongevityView, inheriting from NSView) method is called, which will, at some point, calls the drawRect: method.

Never call drawRect: YOURSELF

Before drawRect: can be called, the graphic context has to be set right (the Mac OS Toolbox equivalent would be a SetPort). That's what the Window Server does, and what you should never try to attempt yourself. If you want drawRect: to be called, call display instead, and the Window Server will do the right thing for you.

The only thing you have to pay attention to is the fact that, the origin is in the lower left corner of the view (this is Display Postscript), instead of the upper left as you might be used to in QuickDraw. To draw, you can mix C-ified Postscript calls such as PSmoveto, PSlineto, PSstroke and framework calls such as drawAtPoint:withAttributes: (of NSString).

Listing 3. Extract from TitleLongevityController.m

- (void)brandSelect:(id)sender
{
  [titleLongevity setBrand:[sender indexOfSelectedItem]];
  [titleLongevity display];
  [nbSelTitles setIntValue:[titleLongevity nbSelTitles]];
}

- (void)seriesSelect:(id)sender
{
  [titleLongevity setSeries:[sender indexOfSelectedItem]];
  [titleLongevity display];
  [nbSelTitles setIntValue:[titleLongevity nbSelTitles]];
}

Listing 3. bis Extract from TitleLongevityView.m

- (void)drawRect:(NSRect)rect
{
  NSString *theString;
  short i, j, startEditMonth, lastEditMonth, tlarr[600];
  // get the right array of titles
  [comicsBase sortArray:array withBrand:brand 
    withSeries:series withKind:1 withState:0 withSort:0];
  nbSelTitles = [array count];
  startEditMonth = [comicsBase startEditMonth];
  lastEditMonth = [comicsBase lastEditMonth];
  // clear and then fill the local array to be graphed
  for(i=0; i<(lastEditMonth-startEditMonth); i++) 
    tlarr[i] = 0;
  for(i=0; i<nbSelTitles; i++)
  {
    CTitle *thisTitle = [array objectAtIndex:i];
    NSMutableArray *theseIssues = [thisTitle issues];
    // if edited then add 1 if there is an issue for this particular edit month
    if (!editOrLong) 
      for(j=0; j < [thisTitle nbIssues]; j++)
        tlarr[[[theseIssues objectAtIndex:j] editMonth] 
          - startEditMonth] += 1;
      // else add 1 for all months between the first published issue to the latest
      else 
        for(j = [[theseIssues objectAtIndex:0] editMonth];
          j <= [[theseIssues lastObject] editMonth]; j++)
            tlarr[j-startEditMonth] += 1;
  }
          - startEditMonth] += 1;
  // else add 1 for all months between the first published issue to the latest
    else 
      for(j = [[theseIssues objectAtIndex:0] editMonth];
        j <= [[theseIssues lastObject] editMonth]; j++)
            tlarr[j-startEditMonth] += 1;
  }
  // draw the axes
  PSsetrgbcolor(0, 0, 0);
  PSmoveto(orx, ory-3); PSlineto(orx, 700); PSstroke();
  PSmoveto(orx-3, ory); PSlineto(700, ory); PSstroke();
  // put the labels on vertical axis
  for(i=10; i<=130; i+=10)
  {
    PSmoveto(orx-3, ory+i*5); 
    PSlineto(700, ory+i*5); 
    PSstroke();
    theString = [NSString stringWithCString:gnums[i]];
    [theString drawAtPoint:NSMakePoint(orx-20, 
    ory-12+i*5) withAttributes:dictionary];
  }
  // put the labels on horizontal axis
  for(i=0; i<(lastEditMonth-startEditMonth+13); i++) 
    if (((i + startEditMonth-1) % 12) == 0)
    {
      j = (i + startEditMonth-1) / 12;
      PSmoveto(orx+i, ory); 
      PSlineto(orx+i, ory-3-((j % 2)?7:0)); PSstroke();
      theString = [NSString stringWithCString:gnums[j]];
      [theString drawAtPoint:NSMakePoint(orx+i-9, 
      ory-21-((j % 2)?7:0)) withAttributes:dictionary];
    }
  // draw the graph in blue
  PSsetrgbcolor(0, 0, 32767);
  for(i=0; i<(lastEditMonth-startEditMonth); i++)
  {
    PSmoveto(orx+i, ory);
    PSlineto(orx+i, ory+tlarr[i]*5);
    PSstroke();
  }
}

CalendarController and CalendarView

The Controller for the "Calendar" window which is a View, and the custom view inheriting from NSView which implements the tabulated display.

This is another very basic use of an NSView. The CalendarView is just some text displayed like a table, and the controller allows, again, the user to change the criteria of the display. There is no big difference from the TitleLongevityView except for one trick. At first, I filled the NSString object to be displayed with the entire text data (with carriage returns), and had only a single drawAtPoint:withAttributes: call. The result was that the lines were reversed from bottom to top (which makes sense when you remember that the origin is in the lower left corner and that the y axis is directed towards the top). Although I could have reversed the text in the NSString object, it made more sense (for easy code reading purposes) to display the text one line at a time, correctly anchored where it should be.

Listing 4. Extract from CalendarView.m

- (void)drawRect:(NSRect)rect
{
  char strtit[] = 
"    OCT NOV DEC 97 FEB MAR APR MAY JUN \ 
    OCT NOV DEC 97 FEB MAR APR MAY JUN \
    OCT NOV DEC 97 FEB MAR APR MAY JUN \n", 
    strlin[200], strstart[7];
  short i, j, k, n, lastmonth, isharr[189][9];

  // get the right array of titles
  [comicsBase sortArray:array withBrand:brand 
    withSeries:series withKind:kind withState:2 
    withSort:sort];
  nbSelTitles = [array count];
  lastmonth = [comicsBase lastEditMonth];
  // fill the correct month names or year value in strtit based upon 
  // the model above
  for(i=1; i<=3; i++) for(j=1; j<=9; j++)
    strncpy(&strtit[6 + (i-1)*45 + (j-1)*4],
      (k = (lastmonth-1-9+j) % 12) ? gmonths[k] : 
      gnums[(lastmonth-1-9+j) / 12], 3);
  // and draw it
  [[NSString stringWithCString:strtit] 
    drawAtPoint:NSMakePoint(0,rect.size.height-20)
    withAttributes:dictionary];

  // clear and fill the local array of issue numbers to be displayed
  for(i=0; i<189; i++) 
    for(j=0; j<9; j++) isharr[i][j] = -1;
      for(i=0; i<nbSelTitles; i++)
      {
        ...  some code irrelevant to the purpose of this 
            article, see the source code for details
      }
  // and display it
  for(i=0; i<63; i++)
  {
    strcpy(strlin, "");
    for(j=i; j < nbSelTitles; j += 63)
    {
      strcpy(strstart, 
        [[[array objectAtIndex:j] abb] cString]);
      while (strlen(strstart) < 6) 
        strcat(strstart, " ");
      strcat(strlin, strstart);
      for(k=0; k<9; k++)
        strcat(strcat(strlin, 
          ((n = isharr[j][k]) < 0)?"  ":
          (char *)gnums[n]), " ");
      strcat(strlin, "  ");
    }
    [[NSString stringWithCString:strlin] 
    drawAtPoint:NSMakePoint(0,rect.size.height-11*(i+3))
    withAttributes:dictionary];
  }
}

InputController

After getting familiar with the framework by doing some basic stuff (what you read above), I eventually reached the level where I wanted to do more ambitious and serious stuff. This Controller (for the "Input" window View) enables the user to modify the Comics Database in a lot of ways.

First, I started with an NSTableView, then implemented the willDisplayCell:forTableColumn:row: method which allowed me to control the graphical aspect (such as fonts and colors, including background colors) of a cell, before objectValueForTableColumn:row: is called.

Then I implemented the setObjectValue:forTableColumn:row: method which allows the user to type in text data in the first 2 columns (the only ones which have been set up as editable in IB).

Listing 5. Extract from InputController.m

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
  return nbRows;
}

- (void)tableView:(NSTableView *)tv 
    willDisplayCell:(id)cell
    forTableColumn:(NSTableColumn *)tableColumn 
    row:(int)row
{
  NSString *identifier = [tableColumn identifier];
  [cell setFont:([identifier cString][0] 
    == 'M') ? fontNonProp : fontProp];
  if ([identifier isEqualToString:@"CurIsh"]) 
    [cell setFont:fontNonProp];
  if ([identifier isEqualToString:@"Empty"])
    if (row == [tv selectedRow])
      [cell setBackgroundColor:[NSColor redColor]];
    else
      [cell setBackgroundColor:[NSColor blackColor]];
  else if ((row == [tv editedRow]) 
            && ([tv columnWithIdentifier:identifier] 
              == [tv editedColumn]))
    [cell setBackgroundColor:[NSColor whiteColor]];
  else
    [cell setBackgroundColor:arrCol[row % 6]];
  [cell setDrawsBackground:YES];
}

- (id)tableView:(NSTableView *)tv 
    objectValueForTableColumn:
      (NSTableColumn *)tableColumn row:(int)row
{
  CIssue *thisIssue;
  CTitle *thisTitle = [array objectAtIndex:row];
  NSString *identifier 

            = [tableColumn identifier], *result;
  thisIssue = [[thisTitle issues] 
    objectAtIndex:[thisTitle 
      findIssue:curIshArray[row]]];
    if ([identifier isEqualToString:@"Abb"]) 
      result = [thisTitle abb];
    else if ([identifier isEqualToString:@"Title"]) 
      result = [thisTitle title];
    else if ([identifier isEqualToString:@"Brand"]) 
      result = [thisTitle brand];
    else if ([identifier isEqualToString:@"Series"]) 
      result = [thisTitle series];
    else if ([identifier isEqualToString:@"State"]) 
      result = [thisTitle tstate];
    else if ([identifier isEqualToString:@"Kind"]) 
      result = [thisTitle kind];
    else if ([identifier isEqualToString:@"CurIsh"])
      result = [NSString 
            stringWithCString:gnums[curIshArray[row]]];
    else if ([identifier isEqualToString:@"Grade"]) 
      result = [thisIssue grade];
    else if ([identifier isEqualToString:@"Type"]) 
      result = [thisIssue ishtype];
    else if ([identifier isEqualToString:@"Content"]) 
      result = [thisIssue content];
    else // column identifiers "M1" to "M6"
    {
      short k, j = -1, 
        i = theEditMonth - 6 
          + [identifier cString][1] - '0';
      for(k = [thisTitle nbIssues]-1; 
      (k >= 0) && (j == -1); k--)
        if ( ([thisIssue = [[thisTitle issues] 
            objectAtIndex:k] editMonth] == i) 
            && !([thisIssue issueFlags] & mskMiss) )
          j = k;
        if (j == -1) result = @"";
        else 
          result = [NSString stringWithCString:gnums 
                    [[thisIssue issueNumber]]];
  }
  return result;
}

At this point, I had a scrolling table with rows of different background colors, and cells with text set up in different fonts, and we could edit textually the first 2 columns.

For the other columns, it made more sense to have popup buttons to allow the user to edit the criteria by choosing rather than by typing. So I implemented a doClick: action method which I connected with IB (this name, doClick, is purely mine, we can call this method any name we want, the important thing is that it must be connected as a target/action method in IB for the NSTableView). In this method, I set up (see the next section) the popup button depending on the row and column of the clicked cell, with both the InputController and PopupInTable classes containing outlets (puit and ic respectively) pointing at each other (connected with IB as usual). Since this popup button is "kinda" floating above the NSTableView, extra care has to be applied to have a good user experience. For instance, it disappears when you click elsewhere: doClick calls releasePub before it sets up a new one, _update (called when the display criteria changed) also calls releasePub.

But that's not enough... If the popup button is displayed and the user scrolls the table, then the image of the popup button scrolls along, but it really still stays where it was, meaning that we get a "phantom" undesirable popup button. To have the popup button disappearing when the user scrolls is a little bit more complex. First, in awakeFromNib, I parsed the superview hierarchy to get the NSScrollView. The documentation states that there is one, but nothing is said about other superviews, and as a matter of fact, between the NSTableView and the NSScrollView, there is an NSClipView; since it is undocumented, I can't rely on the fact that the NSScrollView is 2 levels up in the hierarchy since it could change in the future, hence the little loop:

for ( aView = inputView;
  ![aView isKindOfClass:[NSScrollView class]];
  aView = [aView superview] 
);

When I had the NSScrollView, I saved the current action method and target outlet of its verticalScroller, and I replaced them with the userHasScrolled: action method and self (respectively).

Then in userHasScrolled:, just call releasePub and sendAction: to the previously saved action and target of the verticalScroller (or else, our NSTableView wouldn't scroll anymore...).

Listing 6. Extract from InputController.h

@interface InputController : NSObject
{
  NSScroller *myVerticalScroller;
  id saveVerticalScrollerTarget;
  SEL saveVerticalScrollerAction;
}

Listing 6 bis. Extract from InputController.m

- (void)awakeFromNib
{
  NSView *aView;
  // finding the NSScrollView superview...
  for (aView = inputView; 
    ![aView isKindOfClass:[NSScrollView class]]; 
    aView = [aView superview]);
  myVerticalScroller 
    = [(NSScrollView *)aView verticalScroller];
  // saving the current target and action of the vertical scroller
  // to be able to call them later in userHasScrolled
  saveVerticalScrollerTarget 
    = [myVerticalScroller target];
  saveVerticalScrollerAction 
    = [myVerticalScroller action];

  // set the new target and action. Much more elegant than patching,
  // much more efficient than subclassing.
  [myVerticalScroller setTarget:self];
  [myVerticalScroller 
    setAction:@selector(userHasScrolled:)];
}
- (void)userHasScrolled:(id)sender
{
  // if the user has scrolled then release the pop up button
  [puit releasePub];
  // and call back the original target and action of the vertical scroller
  // so that we actually scroll... (see below)
  [myVerticalScroller 
    sendAction:saveVerticalScrollerAction 
    to:saveVerticalScrollerTarget];
}

The number of lines of code to achieve this interception mechanism is much less than its explanation, and the target/action mechanism is the real power of the Yellow Box framework. With this mechanism, we can add new behaviors without having to subclass (and in this particular case, the subclassing would not have been trivial nor easy).

PopupInTable

This is the Controller for the "floating" popup menu button.

The setUpPopup method creates the appropriate menu items according to the row and column of the clicked cell, memorizing in a separate array the command and attribute for each menu item (easier than trying to extract the information later from the title of the selected menu item). Since I wanted some empty separation menu items in the popup, I discovered that you can't add the same title twice (only one will remain), so that's why I use addItemWithTitle:@"" and then addItemWithTitle:@" " and then addItemWithTitle:@" ", etc.

Disclaimer

This specific usage of a floating popup menu button as I'm using it, is strictly my own user interface. As it goes, some of my colleagues don't like it since they feel it doesn't respect all the proper user interface guidelines. Do not take this sample code as a model for your own user interface in your projects.

The pubSelect: action method modifies the comics database according to the choice selected by the user. The only twist is that, when the user selects the "Other..." menu item, the cell is made editable and the editColumn:row:withEvent:select: method (of NSTableView) is called; then the user has to type in the text data which is then analyzed in the setObjectValue:forTableColumn:row: method (of InputController).

To prevent endless testing of an empty issues array of the CTitle object (in nearly all of the other controllers or CComics and CTitle objects), we forbid the user from deleting the last issue of any title, suggesting deletion of the title itself instead. For the same reason, when the user creates a new title, it comes with a first issue automatically.

Since the source code for both setUpPopup and pubSelect: is way too long for me to insert in these pages, I invite you, instead, to take a look at it directly.

For speed optimization, since a reloadData method call to the NSTableView would be costly (there are a lot of rows and columns all with different fonts and background colors in our NSTableView), and since the deselection of a currently edited row forces the redisplay of that row calling objectValueForTableColumn:row: for each cell of this row, I call, where appropriate, the sequence deselectRow:/selectRow:byExtendingSelection: to always have a valid display of the edited row.

Conclusion

You'll find out, using the Yellow Box framework, that you don't have to subclass as much as you would using a C++ framework. The dynamic binding, coming from Objective-C, enables us to rely more on the concepts of target/action, delegation and notification. This dynamic binding has its advantages and its drawbacks: you can inspect objects at runtime to determine their abilities, thus use objects that you never knew about as long as they respond to the appropriate messages, reuse objects much more easily, etc. On the downside, you can't have as much strong type checking at compilation time than you would get with C++, which may lead to interesting experiences at debugging time.

As far as notifications go, the Yellow Box mechanism is very simple to use. Whenever the content of the database changes, the ImputController object will send a notification this way:

[[NSNotificationCenter defaultCenter] 
  postNotificationName: ComicsDidChange object: self
];

Any other Controller (Verify, Calendar, Browser, TitleLongevity), wishing to be informed has just to register for this notification, in its init method, for example:

[[NSNotificationCenter defaultCenter] 
  addObserver: self selector: @selector(comicsChanged:) 
  name: ComicsDidChange 
  object: nil
];

then its comicsChanged: method will be called to do whatever appropriate to change the display of the window, whenever the InputController sends the notification. The only thing to remember is to unregister this notification in the Controller's dealloc method or else the Notification manager will try to send a notification to a released object.

What could be more simple?

Thanks to our technical reviewers Michelle Wyner, Deborah Grits, Andy Bachorsky, Andy Belk, Alex Dosher, Tony Frey and Randy Nelson.


Éric Simenel is really happy he transferred in Cupertino's DTS from Paris' DTS. Aside from the fact that he got a real good welcome from his current colleagues, he's getting much more sun here than there, and, due to his constant location here, he has easier access to Comic Books Conventions where he completed many runs... The current mark is at 22,000 and counting.

 
AAPL
$517.96
Apple Inc.
-3.72
MSFT
$39.75
Microsoft Corpora
+0.57
GOOG
$536.44
Google Inc.
+3.92

MacTech Search:
Community Search:

Software Updates via MacUpdate

Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
MYStuff Pro 2.0.16 - Create inventories...
MYStuff Pro is the most flexible way to create detail-rich inventories for your home or small business. Add items to MYStuff by dragging and dropping existing information, uploading new images, or... Read more
TurboTax 2013.r17.002 - Manage your 2013...
TurboTax guides you through your tax return step by step, does all the calculations, and checks your return for errors and overlooked deductions. It lets you file your return electronically to get... Read more
TrailRunner 3.8.769 - Route planning for...
Note: While the software is classified as freeware, it is actually donationware. Please consider making a donation to help support development. TrailRunner is the perfect companion for runners,... Read more
Flavours 1.1.10 - Create and apply theme...
Flavours is a Mac application that allow users to create, apply and share beautifully designed themes. Classy Give your Mac a gorgeous new look by applying delicious themes! Easy Unleash your... Read more
Spotify 0.9.8.296. - Stream music, creat...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue... Read more
SlingPlayer Plugin 3.3.20.475 - Browser...
SlingPlayer is the screen interface software that works hand-in-hand with the hardware inside the Slingbox to make your TV viewing experience just like that at home. It features an array of... Read more
S.M.A.R.T. for USB and FireWire Drives 0...
S.M.A.R.T. for USB and FireWire Drives is a kernel driver for OS X external usb or firewire drives. It extends the standard driver behaviour by providing access to drive smart data. The interface to... Read more

Latest Forum Discussions

See All

148Apps Live on Twitch: Pivvot’s Looper...
On our latest Twitch stream, we’ll be playing a pair of minimalist arcade games, one that just got a big content update in Pivvot, and another that was inspired by it in 15 Coins. Whitaker Trebella, creator of Pivvot, will discuss the new modes... | Read more »
Word Cubes Review
Word Cubes Review By Jordan Minor on April 15th, 2014 Our Rating: :: SQUARESVILLEUniversal App - Designed for iPhone and iPad Word Cubes is fine, but it is barely any different from any other word game.   | Read more »
PAX East 2014 – Desert Fox: The Battle o...
PAX East 2014 – Desert Fox: The Battle of El Alamein is Coming to iOS Soon Posted by Rob Rich on April 15th, 2014 [ permalink ] Shenandoah Studio has become one of the go-to developers for war games on iOS, with | Read more »
Tank of Tanks Review
Tank of Tanks Review By Carter Dotson on April 15th, 2014 Our Rating: :: TANKS A LOT!iPad Only App - Designed for the iPad This multiplayer game played on a single iPad is simple, chaotic fun.   | Read more »
PAX East 2014 – Dungeon of the Endless J...
PAX East 2014 – Dungeon of the Endless Just Might Have a Shot at an iPad Release Posted by Rob Rich on April 15th, 2014 [ permalink ] I think it’s fair to say that | Read more »
SideSwype Review
SideSwype Review By Carter Dotson on April 15th, 2014 Our Rating: :: ON YOUR SIDEUniversal App - Designed for iPhone and iPad SideSwype is a puzzler that takes inspiration from Threes, but becomes its own incredibly fun game.   | Read more »
PAX East 2014 – Bigfoot Hunter Invites P...
PAX East 2014 – Bigfoot Hunter Invites Players on a Wild and Wooly Photo Safari Posted by Rob Rich on April 15th, 2014 [ permalink ] Yeti. Sasquatch. Wendigo. | Read more »
Dungeon Quest Review
Dungeon Quest Review By Cata Modorcea on April 15th, 2014 Our Rating: :: NO STORY, BUT GOOD FUNUniversal App - Designed for iPhone and iPad Dungeon Quest does a lot of things right, but ultimately forgets about one of the core... | Read more »
Tempo AI and Speek Join Forces to “Kill...
Tempo AI and Speek Join Forces to “Kill the Conference Call PIN” Posted by Rob Rich on April 15th, 2014 [ permalink ] Today Tempo AI, makers of Tempo Smart Calendar, and | Read more »
Fighting Fantasy: Starship Traveller Rev...
Fighting Fantasy: Starship Traveller Review By Jennifer Allen on April 15th, 2014 Our Rating: :: A SIGNIFICANT VOYAGEUniversal App - Designed for iPhone and iPad Continuing the release of Fighting Fantasy titles, Starship Traveller... | Read more »

Price Scanner via MacPrices.net

Save $50 on Mac mini Server
B&H Photo has the 2012 Mac mini Server on sale for $949 including free shipping plus NY sales tax only. Their price is $50 off MSRP. Read more
PhatWare’s “Ultimate Writing App For iOS” Ren...
PhatWare Corp. has announced it has renamed its new WritePro word processing app for iPhone and iPad: WritePad Pro. The decision to change the app’s name to leverages the strong brand awareness and... Read more
Full Resolution Photo Editor Tint Mint 1.0 Re...
California based independent developer, Jeffrey Sun, creator of the iOS app Modern Editor, has released Tint Mint, a new photography app for editing enthusiasts. The app costs a dollar, and it packs... Read more
16GB iPad mini (Apple refurbished) available...
The Apple Store has refurbished 1st generation 16GB iPad minis available for $249 including free shipping. Both black and white models are available. Read more
Save $120 on the 27-inch 3.2GHz Haswell iMac
B&H Photo has the 27″ 3.2GHz iMac on sale for $1679.99 including free shipping plus NY sales tax only. Their price is about $120 off MSRP. Read more
Using a Mac Doesn’t Eliminate The Heartbleed...
Low End Mac’s Dan Knight notes that any time you visit a website with an https: prefix or see that secure lock icon on your browser, some type of security software is busy trying to protect your data... Read more
AirPrint Basics Tutorial Posted
A new Apple Knowledge Base article helps get you started using AirPrint, the Apple protocol that enables instant printing from iPad, iPhone, iPod touch, and Mac without the need to install drivers or... Read more
Speed Tips For Running LibreOffice On The Mac
LibreOffice is my favorite of several free, open-source application suites, and the one I have configured on my Mac as my default app for Word documents that one frequently has to deal with. It also... Read more
Snag a 15-inch Retina MacBook Pro for $115 of...
B&H Photo has 2013 15″ Retina MacBook Pros on sale for up to $115 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.3GHz Retina MacBook Pro: $2489.99... Read more
MacBook Airs on sale for $50 to $100 off MSRP
Several resellers are offering $50-$100 discounts on 11″ and 13″ MacBook Airs today, including Amazon, Best Buy, B&H, and others. See the breakdown of deals on our MacBook Air Price Tracker,... Read more

Jobs Board

*Apple* Retail - Manager - SoHo - Apple (Uni...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** Every day, business customers come to the Apple Store to discover what powerful, easy-to-use Apple products can do for them. As a Business Leader, Read more
Position Opening at *Apple* - Apple (United...
…challenges of developing individuals, building teams, and affecting growth across Apple Stores. You demonstrate successful leadership ability - focusing on excellence Read more
Position Opening at *Apple* - Apple (United...
…Summary** As a Specialist, you help create the energy and excitement around Apple products, providing the right solutions and getting products into customers' hands. You Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.