Skip navigation.
 
mlRe: NSMatrix
FROM : Dave Hersey
DATE : Wed Mar 26 19:40:52 2008

On Mar 26, 2008, at 11:52 AM, Matthew Miller wrote:
> Could some please explain in simple terms how to create a NSMatrix 
> which distributes cells in rows based on the input of a table and 
> takes away the cells when it is not needed. Thank you very much!
>
> P.S. I am new to cocoa so please don't use really big and complex 
> cocoa vocab, but then again, I would appreciate anything! :)


The problem is that NSMatrix is not a beginner class. If you don't 
have a solid understanding of other Cocoa frameworks like NSControl, 
NSView, NSCell (and its variants- NSActionCell, NSImageCell), and if 
you aren't comfortable subclassing yet, you're going to have a lot of 
trouble with NSMatrix.

But, here's what you need to do.

First, you need to do some design.

1. Create a subclass for the data that goes in the cells. While not 
strictly necessary (e.g. you could create a matrix of strings using 
just NSString as the object class), you almost always will want to do 
this. Typically you'll have information that won't be displayed, such 
as a path to an image, and you'll want to keep that connected to the 
data you are displaying. So, we'll create an NSObject subclass to hold 
our data and call it ThumbnailObject.

2. Decide where you're going to store that data. NSArrayController is 
a great way to manage contents of an NSMatrix, but if you're not 
familiar with that, I'd go with an NSMutableArray for now. Your 
ThumbnailObjects will be stored in the array as they are created, and 
removed from it when they're tossed aside. The Matrix will reflect the 
objects that are stored in this array.

3. Decide what your cell class will be for the matrix. For example, if 
you were displaying a bunch of images, you might use NSImageCell. If 
you were displaying text, you might use NSTextFieldCell. If you're 
displaying checkboxes, you might use NSButtonCell. However, unless 
your matrix needs are very basic, you'll probably want to subclass 
this base cell class. Do you absolutely need to? Maybe not, but it 
will probably make your code easier to follow and maintain. So, let's 
say you're going to display images. You'd probably want to subclass 
NSImageCell or NSActionCell for that. Which you choose is an 
implementation detail, and determines the methods you need to override 
or provide in order to have the cell behave the way you want. For this 
discussion, we'll create a subclass of NSImageCell called ThumbnailCell.

4. Create a subclass of NSMatrix. You'll almost certainly need this, 
since NSMatrix is a rather peculiar class that almost feels 
"unfinished" when you get working with it. We'll call our subclass of 
NSMatrix ThumbnailMatrix.

So, in this design, you have a ThumbnailMatrix which contains 
ThumbnailCells that display data from ThumbnailObjects. You've created 
subclasses of NSMatrix, NSImageCell and NSObject to do this.

You've got to get the design above nailed down before going any 
further. If any of that is confusing or doesn't make sense to you, 
read up on the classes mentioned above before proceeding.

Now you code.

5. Code the subclasses.

The ThumbnailMatrix class is going to have the following methods:

initWithFrame - Create an instance of ThumbnailCell and pass it to the 
superclass's initWithFrame method as the prototype cell for your matrix.

awakeFromNib - Here, I'd set up things like interCellSpacing, 
cellSize, autoScroll, etc.

renewRowsIfNeeded - This is a method I create that checks to see if 
the number of cells that can fit on the rows has changed or if the 
width between them has changed. (I usually spread the extra space at 
the end of the row between the cells to make them appear evenly 
distributed.) Get the cell size and the super view's bounds, then 
determine how many rows and columns fit. Get the "left over" space 
from a full row and use that to determine what the interCell spacing 
should be, then set the interCell spacing if it's different. Compare 
the number of rows and columns to what you already have. If they're 
different you need to call renewRows. Return true if the intercell 
spacing changed or renewRows was called.

setFrameSize - Override this if you want to have the matrix do live 
rebuilding as you grow or shrink the window. If you don't do this, the 
matrix will stay it's old size until the mouse button is released, at 
which point it will change its number of rows and columns to fit 
(because of viewDidEndLiveResize below). It looks nice to have the 
matrix change to reflect the final state while you're dragging, so I'd 
implement this. It should call it's super then, if  renewRowsIfNeeded 
returns true, call sizeToCells. Then call setNeedsDisplay (or 
setNeedsDisplayInRect if you're optimizing for live resizing.) Do 
setNeedsDisplay for now, then look into live resizing optimization.

viewDidEndLiveResize - When the mouse is released after a resize, this 
method is called. If renewRowsIfNeeded returns true, then call 
sizeToCells, setNeedsDisplay.

rebuildThumbnailMatrix - This is a method I'd implement to get your 
data into the matrix. It basically needs to go through the array 
mentioned in step 2, create enough cells if there aren't enough 
already, then store a ThumbnailObject in each cell. At the end, you 
need to do the renewRowsIfNeeded, sizeToCells, setNeedsDisplay mantra.

There are several other things you'll probably end up wanting to do, 
for example to support drag and drop. But, get this stuff working 
first and then search the lists about that.

The ThumbnailCell class is going to have these methods:

dealloc - releases stored thumbnailObject, calls super
copyWithZone - returns a new cell from super copyWithZone, retaining 
the ThumbnailObject and storing that in the new cell
thumbnailObject - returns the stored thumbnailObject
setThumbnailObject - stores the cell's thumbnailObject
image - returns the stored thumbnailObject's image
setImage - sets the stored thumbnailObject's image and sends it to 
super setImage
drawInteriorWithFrame - if no thumbnailObject is set, drops out. (A 
matrix can have unused cells at the end of the last row) Otherwise, 
checks to see if [super image] == [self image]. If not, call [super 
setImage: [self image]] to store the thumbnailObject's image in the 
super. Either way, call super drawInteriorWithFrame if the thumbnail 
object for the cell is not nil.

You'll probably want to override more methods to control drawing.

The ThumbnailObject class is going to simply have storage and 
accessors for your thumbnail data. In this case you might have
init - create an NSMutableDictionary to hold the object's data
dealloc - clean up
image - return the dictionary's entry for the object's image
setImage - store the passed image in the dictionary
pathToImage - return the dictionary's entry for the image's path.
setPathToImage - store the passed image path in the dictionary

This is by no means an optimal way to do things since eventually you'd 
have all of your images in memory. However, you could implement some 
"smarts" to unload images that are well out of view range and reload 
them when they're asked for, etc. Get it working, then optimize it.

So, at this point you have all of your classes created. In your app 
controller, you'll need an outlet to your matrix. At some point you'll 
call rebuildThumbnailMatrix (above) with the data you want to put in 
the matrix. If you delete objects or add more later, you'll want to 
call rebuildThumbnailMatrix again.

Now you create a matrix in InterfaceBuilder.

6. Go to Interface Builder and create a matrix in a window. In IB3 
you'll create an instance of an NSImageView and then embed it in a 
matrix and then embed that in a scroll view. Change the matrix's type 
to ThumbnailMatrix. Connect your application controller's outlet to 
the matrix.

7. Build and debug and debug and debug.
8. Debug more, you missed something.
9. It works, optimize, you're done.

Of course there are other ways to do some of this, but that's the 
basic approach you'll need to follow.

I think this is probably way beyond where you're at right now with 
Cocoa, but use it as a guide for how to approach learning the 
material. It probably looks overwhelming, but once you're comfortable 
with the underlying Cocoa frameworks it will be manageable.

- d

Related mailsAuthorDate
mlNSMatrix Matthew Miller Mar 26, 16:52
mlRe: NSMatrix Dave Hersey Mar 26, 19:40
mlRe: NSMatrix Erik Buck Mar 26, 21:42
mlRe: NSMatrix Dave Hersey Mar 26, 22:08