Setting image + text in tableview's cell

Discussion in 'Mac Programming' started by Danneman101, Jan 15, 2011.

  1. Danneman101 macrumors 6502

    Joined:
    Aug 14, 2008
    #1
    The last day or so I've been reading various approaches on how to add an image in a tableview's cell (with the text remaining).


    APPROACH 1:

    According to the NSCell Class Reference, it should be as easy as using the "setImage"-function:

    Code:
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumn row: (int)row
    {
    	[cell setImage:[NSImage imageNamed:@"myIcon.png"]];
    }
    
    This however results in cells without text nor image (empty cell).


    APPROACH 2:

    Reading some more in the docs, you come across the "initImageCell" which returns a cell that is initialized with an image. So I tried converting the current cell to an image-cell by using this code:

    Code:
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumn row: (int)row
    {
    	(NSImageCell*)cell = [cell initImageCell:[NSImage imageNamed:@"myIcon.png"]];	
    }
    
    This also results in cells without text nor image (empty cell).

    Also, the cell-type continues to be "NSTextFieldCell", so obviously the object-cast did not work.


    APPROACH 3:

    Since the reference docs did not give me what I needed (or I did not understand them in order to discern what I needed from them - which admittedly is the more probable case), I tried reading some of the code-samples apple provides.

    I came across a Cocoa Extension class called "ImageAndTextCell.h", which I included in my project and referenced using this code that I swiped from the samples, and hopefully should convert the cell to an ImageAndTextCell-type with the image in question:

    Code:
    #import "ImageAndTextCell.h"
    ....
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumn row: (int)row
    {
    	NSImage *image1 = [[NSImage alloc] initWithContentsOfFile:@"myIcon.png"];
    	[(ImageAndTextCell*)cell setImage:image1];
    }
    
    This code only resulted in the text appearing, without an image. So no change at all.

    And to top it off, the class of the cell continues to be "NSTextFieldCell".


    Well, that's the three most coherent approaches I've tried without any success so far (there's been plenty more hair-pulling-inducing attempts, like constructing a view w image and adding it to the cell, but I'll leave it at that). Any ideas?
     
  2. kainjow, Jan 15, 2011
    Last edited: Jan 15, 2011

    kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    This class is the way to go if you can't figure out how to do it from scratch. It's been around forever and works.

    Casting to another type doesn't convert the object. This suggests you're not understanding some fundamentals about Objective-C/C that you may want to read up on. Read this.

    I suggest studying the sample code Apple provides which shows how to use this code:
    http://developer.apple.com/library/mac/#samplecode/ImageBackground/Introduction/Intro.html
     
  3. Danneman101, Jan 15, 2011
    Last edited: Jan 15, 2011

    Danneman101 thread starter macrumors 6502

    Joined:
    Aug 14, 2008
    #3
    But how come the NSCell-function "setImage" does not work in my case? It seems to be the recommended way by the reference-docs to do things, so is the lack of an appearing image due to the cell being initiated by default to a cell that can't display an image (ie. a NSTextFieldCell), or is there some other piece of the pussle missing?


    [edit]

    I've managed to study the codesample you linked to, and it turns out that a tableview's cells need to be initialized as "ImageAndTextCell"s in, say, the awake-function. This is due to the fact that the default cell can not load any images, just text - which also answers the question above the edit-tag.

    This being the fact I set up my initialization-code for the cells as such:

    Code:
    - (void)awakeFromNib 
    {
    	NSTableColumn* tableColumn = [tableView tableColumnWithIdentifier:@"MenuColumn"];
    	ImageAndTextCell* imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
    	[tableColumn setDataCell:imageAndTextCell];
    }
    
    My tableview is called "tableView" and its only column name and identifier are both "MenuColumn" (set in the xib-file).

    This should, as far as I understand, set the column to display "ImageAndTextCell"s.

    So, later in the code I try setting the image as thus:

    Code:
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumn row: (int)row
    {
    	NSImage *image1 = [[NSImage alloc] initWithContentsOfFile:@"myIcon.png"];
    	[(ImageAndTextCell*)cell setImage:image1];
    }
    

    This still results in only the text being displayed :(
     
  4. Danneman101 thread starter macrumors 6502

    Joined:
    Aug 14, 2008
    #4
    Never let it be said I'm a quitter - 3'd day and finally got it working *phew*.

    Two solutions finally worked:


    SOLUTION 1: Changing NSCells to NSBrowserCells

    Code:
    ....
    
    - (void)awakeFromNib 
    {
    	NSTableColumn* col = [[tableView tableColumns] objectAtIndex: 0]; // Only have one col so this is ok
    	NSBrowserCell* cell = [[NSBrowserCell alloc] init];
    	[cell setLeaf: YES];
    	[col setDataCell: cell];
    }
    
    ....
    
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumn row: (int)row 
    {
    	NSImage *img = [NSImage imageNamed:@"myIcon.png"];
    	[(NSBrowserCell*)cell setImage:img];	
    }
    

    SOLUTION 2: Changing NSCells to ImageAndTextCells.

    This requires the inclusion of the "ImageAndTextCell.h/m".

    Code:
    #import "ImageAndTextCell.h"
    
    ....
    
    - (void)awakeFromNib 
    {
    	NSTableColumn* tableColumn = [tableView tableColumnWithIdentifier:@"MenuColumn"];
    	ImageAndTextCell* imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
    	[tableColumn setDataCell:imageAndTextCell];
    }
    
    ....
    
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumn row: (int)row 
    {
    	NSImage *img = [NSImage imageNamed:@"myIcon.png"];
    	[(ImageAndTextCell*)cell setImage:img];	
    }
    

    There you go :) And thanks to kainjow - the codesample was crucial, although I'm generally amazed at the poor documentation of all samples that Apple provide, making it harder than necessary for newbies like me trying to learn to discern any understanding from them.

    Wonder which one is the best to use, though. Have read some posts from people recommending not to use the NSBrowserCell, but giving little input as to why...
     
  5. wittegijt macrumors member

    Joined:
    Feb 18, 2007
    Location:
    Eindhoven
    #5
    NSBrowserCell subclasses NSCell, not NSActionCell. Up to 10.5, setObjectValue / objectValue was a member of NSActionCell, not NSCell. And the cell values are needed for key value coding, bindings etc.

    Wittegijt
     
  6. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #6
    setObjectValue has been part of NSCell since the beginning. It's just that from 10.0-10.5 there was specific behavior when calling that on NSActionCell, but it appears to not do anything different now on 10.6.

    Technically you could use NSBrowserCell if it suites your needs, but it's not really supposed to be used outside of an NSBrowser. It also has less control over how it draws text, since as wittegijt mentioned it's subclassed from NSCell whereas ImageAndTextCell is subclassed from NSTextFieldCell.
     
  7. Danneman101 thread starter macrumors 6502

    Joined:
    Aug 14, 2008
    #7
    Ah, so with NSTextFieldCell you get some more functionality than with NSCell since it inherits from that class. Nice, I'll go for solution 2 then :)

    I've scoured both the NSCell and NSTextFieldCell class references, but one thing that eludes me is how to set the vertical alignment of the text.

    The horisontal can be done using NSCell:setAlignment, and the vertical alignment of the image is set to center automatically. But the text appears to be aligned to the top at default.
     
  8. Danneman101, Jan 16, 2011
    Last edited: Jan 16, 2011

    Danneman101 thread starter macrumors 6502

    Joined:
    Aug 14, 2008
    #8
    Vertical align ok

    Well, it appears Apple did not think vertical alignment was a big deal when they constructed (or ever since updated) their classes, so now you'll have to add yet another class from which you can subclass the cell.

    It's described very neatly here:
    http://www.red-sweater.com/blog/148/what-a-difference-a-cell-makes

    All I did to include this in my code was to add the files to my project, and add an extra column ("MenuColumnIcon") to the tableview in my xib-file.

    The code was then adjusted in the following manner:

    Code:
    #import "ImageAndTextCell.h"
    #import "RSVerticallyCenteredTextFieldCell.h" // NEW
    
    ....
    
    - (void)awakeFromNib 
    {
    	// Image Column to the left
    	NSTableColumn* tableColumnIcon = [tableView tableColumnWithIdentifier:@"MenuColumnIcon"];  // EDITED
    	ImageAndTextCell* imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
    	[tableColumnIcon setDataCell:imageAndTextCell];  // EDITED
    
    	// Text Column to the right - sets the text to align vertically to the middle  // NEW
    	NSTableColumn* tableColumn = [tableView tableColumnWithIdentifier:@"MenuColumn"];
    	RSVerticallyCenteredTextFieldCell* rsVerticallyCenteredTextFieldCell = [[[RSVerticallyCenteredTextFieldCell alloc] init] autorelease];
    	[tableColumn setDataCell:rsVerticallyCenteredTextFieldCell];
    }
    
    ....
    
    - (void)tableView: (NSTableView *)tableView willDisplayCell: (id)cell forTableColumn: (NSTableColumn *)tableColumnIcon row: (int)row  // EDITED
    {
    	NSImage *img = [NSImage imageNamed:@"myIcon.png"];
    	[(ImageAndTextCell*)cell setImage:img];	
    }
    

    "Simple" as that. Well, if Apple had provided this functionality in the first place to NSCell that would have been simple. Now it's just tedious. On the upside, however, I learned a lot about Obj-C/Cocoa/XCode that I probably wouldn't have hadn't it been such a pain in the ass to get this to work ;)

    Still there's something bugging me with the appearance of my tableview. There is a selection-effect going on, surrounding my entire tableview with blue (much like when you input text into this textfield which Im just writing into), and the very last cell is always selected and set with a blue background. Seems a bit odd, and certainly something I want to get rid of.

    Any suggestions before I venture on yet another investigative journey trough the spectacularly peculiar world that is Mac programming? :)


    [EDIT]
    Just realized that the new class did not truncate text, so you'll have to set that yourself in the willDisplayCell:

    Code:
    	[cell setLineBreakMode:NSLineBreakByTruncatingTail];
    
     
  9. Comrade Yeti macrumors newbie

    Joined:
    Nov 3, 2010
    #9
    In IB, you want to change the focus ring of your tableview to none. Make sure you change this for the tableview and not the scroll view that encloses it.
     
  10. Danneman101 thread starter macrumors 6502

    Joined:
    Aug 14, 2008
    #10
    Thanks for the input :) I've actually tried that solution, however, and although it did remove the blue rings around my components the last row was always being selected.

    Finally I resorted to another solution - I removed all selectionhighlights in the tableview by using the following code in the awake-function.

    Code:
    [tableView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
    
    So now I had a situation where nothing in the table got selected or hightlighted. I solved this by adding a third row to my tableview into which I inserted an image (an arrow) for the selected row in my willDisplayCell-function. So the net result ended up being much more aesthetic than it would have had I simply found a way to use the default selection (blue background) :)

    A bit tedious, as this should be automated but somehow my project seems to disable that functionality, but on the bright side all this inventing workarounds is finally getting me to know obj-c better :)
     

Share This Page