September 09, 2005
NSOutlineView, reloading items, and the expansion state
NSOutlineView requires all of the items in it to be pointer unique. If they are not, strange things happen. However, they can be equal (meaning [NSObject isEqual] may return YES).
However, there is a small exception to that rule. If you do a reload, the expansion state of items will be preserved. This expansion state is done by placing the current expanded items into an NSMutableSet. After a reload happens, if an item is in that set it is shown expanded.
What does this mean? Well, to see if an item should be expanded or not, it is looked up in the set. This is done via the item's hashcode and an isEqual comparision. Ahh ha! NSOutlineView is doing some non-pointer unique things here. This means you could potentially switch out items during a reload, and they would still appear expanded after the reload (as long as they have the same hashcode and are isEqual). Another nasty side effect: any items which mutate their hashtable will not appear expanded after a reload! For instance, if you modify an NSDictionary, its hash code will change. Take this point into consideration: the objects you put into an NSOutlineView are NSMutableDictionaries. You do a reload, and after the reload you add a child to one of the items via a key in the NSDictionary. Therefore, the NSMutableDictionary now has a different hashcode, and it will no longer appear expanded! The easy way to work around this is to use a non-mutable object in the outlineview (such as your own object, which might have a dictionary inside of it to keep track of things). Just a neat tip, for those who care or run into this.
Posted by corbin at 03:54 PM | Comments (0)
August 01, 2005
Using Xcode to become a faster programmer
You can utilize some of Xcode's cool features to become a faster Mac OS X programmer. Here are some things which you may not know:
1. Use Code Completion (Code Sense). However, the default keyboard shortcut is lame. Go to the Xcode preferences, Key Bindings:

and change the Code Sense Completion List binding to be Ctrl-Space (I use Option-Space, but really it is the ctrl-key, because I like to swap my ctrl and option keys):

While you are there, set the Code Sense Select Next Placeholder to be Ctrl-/ (that is control forward slash, and again, I have mine set to be option-/ because I swap my keys):
![]()
2. Now that you have it properly setup, use it! Here is how you should be using it:
You want to call a particular method in your current class (self). Type the first few characters:
![]()
Hit Ctrl-Space to bring up the Code Sense window:

Type a few more characters to narrow down what you want, and arrow key down to select the signature you want to use:

Hit enter to add the template into your source code:
![]()
Fill in the first parameter:
![]()
Then, hit Ctrl-/ to go the next completion item and highlight it:
![]()
Repeat with more Ctrl-/ commands until you are done.
3. Congratulations! You are now a faster programmer.
--corbin
Posted by corbin at 10:44 PM | Comments (2)
Cmd - click. The subtle secret of Mac OS X.
It seems that a lot of people don't know how cmd click works in a lot of Mac OS X Cocoa applications. To put it simply, Cmd-clicking on a non key window (or application for that matter) will act like a normal click without making the window key! This is really cool for testing UI things while debugging or testing for memory leaks.
Here is how I use this technique while debugging:
1. I'm debugging my application with Xcode. Inside of Xcode I have a “hot” breakpoint that I don't want to enable, since enabling it will make Xcode key, and the key/focus switching will hit the breakpoint at the wrong time.
2. Because of this, I keep my application key, and Cmd-click on the title bar in Xcode to move the window to where I can see the breakpoint.
3. Then, I Cmd-click the breakpoint, enabling it without setting the window key and never leaving focus from my application that i want to debug.
4. Now, I perform the operation that invokes the breakpoint and debug away.
Sure, you could do this by figuring out how many times the breakpoint was hit, and in gdb ignore the breakpoint for X times, but sometimes that is a pain to do.
I also use this technique while trying to find memory leaks with Object Alloc. Frequently, focus switching causes numerous allocations, and by Cmd-clicking the “Mark” button in Object Alloc I can prevent those allocations from happening.
Note that cmd click doesn't work 100% correctly with certain components. For instance, NSTableView won't change the selected item unless it is key (note that that bug will be fixed).
--corbin
Posted by corbin at 03:01 PM | Comments (1)
July 28, 2005
Drag and Drop in an NSTableView
Drag and Drop in an NSTableView is easy to do. However, I think the documentation (Table Views: Using Drag and Drop in Tables) for it isn't particularly great. It misses a few points, so I'm going to go over the basic steps on how to add drag and drop to your TableView. Here, I'll assume you have a TableView with your source code controller class set as the delegate.
Declare your custom pasteboard format:
#define BasicTableViewDragAndDropDataType @"BasicTableViewDragAndDropDataType"
In awakeFromNib you must register for the drag types you want to receive (you could have others here):
- (void)awakeFromNib {
[myTableView registerForDraggedTypes:[NSArray arrayWithObjects:BasicTableViewDragAndDropDataType, nil]];
}
Then, you must implement writeRowsWithIndexes to add your data to the pasteboard:
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard {
// Drag and drop support
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
[pboard declareTypes:[NSArray arrayWithObject:BasicTableViewDragAndDropDataType] owner:self];
[pboard setData:data forType:BasicTableViewDragAndDropDataType];
return YES;
}
Next you will validate the drop:
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op {
// Add code here to validate the drop
NSLog(@"validate Drop");
return NSDragOperationEvery;
}
Finally, you must have a method that accepts the drop. Here you could access the BasicTableViewDragAndDropDataType and look at the rows that were dragged.
- (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)op {
NSLog(@"acceptDrop");
// Add code here to accept the drop
return YES;
}
And that is it! It is pretty easy...
Technorati Tags: Cocoa
Posted by corbin at 06:44 PM | Comments (1) | TrackBack
July 26, 2005
Dynamically populating an NSPopUpButtonCell in an NSTableView
It is quite common. You have a PopUpButton (NSPopUpButtonCell) in an NSTableView and you want to dynamically change the contents based on the selected row:
How do you do this? There are a few tricky steps. First, add a menu to the nib and set the delegate for the menu to be your Controller:
Okay. Next is the tricky part. In IB, when you have a column in a tableview selected, it has a little white triangle in the corner:
Clicking on that will allow you to modify properties of the cell (in this case, the NSPopUpButtonCell). However, we want to set the menu for the NSPopUpButtonCell, so drag from that little triangle to your menu and set the menu outlet:
Okay, good! Now, all your controller needs is a little bit of code to dynamically populate the menu:
- (void)menuNeedsUpdate:(NSMenu *)menu {
// remove all previous items
while ([menu numberOfItems] > 0) {
[menu removeItemAtIndex:0];
}
// dynamically build the menu
int i;
int selectedRow = [tableViewStuff selectedRow];
for (i = 0; i <= selectedRow; i++) {
NSMenuItem *item = [[NSMenuItem alloc]
initWithTitle:[NSString stringWithFormat:@“Menu %d”, i]
action:@selector(menuClicked:) keyEquivalent:@“”];
[item setTarget:self];
[menu addItem:item];
}
}
Okay, that should work, right? Normally, yes. But in a tableview when a cell is tracked, it is first copied. Unfortunately, a small bug in the menu code doesn't copy the delegate. Therefore, we must fix it up in the nstableview's delegate method:
- (void)tableView:(NSTableView *)tableView
willDisplayCell:(id)cell
forTableColumn:(NSTableColumn *)tableColumn
row:(int)row
{
if ([cell isKindOfClass:[NSPopUpButtonCell class]]) {
[[cell menu] setDelegate:self];
}
}
That's it! Have fun...happy coding.
Posted by corbin at 04:14 PM | Comments (0)
July 08, 2005
Adding a popup menu to any NSButton
It is easy to add a popup to any button (NSButton).
Here are the steps:
- In your nib, drop down an NSPopupButton and add the items you want to its popup list.
- Set the NSPopupButton to be hidden.
- Add an outlet for the NSPopupButton (named popupButton in this case)
- Drop down an NSButton.
- Set the image (or whatever else you want) for the button
- In the action for the button, run the following code:
[[popupButton cell] performClickWithFrame:[sender frame] inView:[sender superview]];
That is it!
Posted by corbin at 04:07 PM | Comments (8)
June 30, 2005
Different cells on each row in an NSTableView or NSOutlineView
Some people have asked me how to dynamically change the cell that is displayed for each row in an NSTableView or NSOutlineView. Generally, the same cell is used for each row, but it is possible to use a different cell for each row, if you like. NSTableColumn can change the cell that is used for each row. NSTableView calls -[NSTableColumn dataCellForRow:(int)row] for each row. Now, consider this code:
@interface NSObject (VariableCellColumnDelegate)
- (id)tableColumn:(NSTableColumn *)column inTableView:(NSTableView *)tableView dataCellForRow:(int)row;
@end
@interface VariableCellColumn : NSTableColumn
@end
@implementation VariableCellColumn
- (id)dataCellForRow:(int)row {
id delegate = [[self tableView] delegate];
if ([delegate respondsToSelector:@selector(tableColumn:inTableView:dataCellForRow:)]) {
return [delegate tableColumn:self inTableView:[self tableView] dataCellForRow:row];
} else {
return [super dataCellForRow:row];
}
}
@end
If the delegate simply implements the method:
- (id)tableColumn:(NSTableColumn *)column inTableView:(NSTableView *)tableView dataCellForRow:(int)row;
...you will be able to dynamically change the cells, with very little effort! In IB, you can set the class for the NSTableColumn to be VariableCellColumn (after dragging the header into IB), and you won't have to do any work of dynamically creating NSTableColumn's.
Posted by corbin at 03:28 PM | Comments (0)
June 20, 2005
Debugging OCTest bundles
To debug OCTest bundles:
1. Add a new executable to your Xcode project pointing it to "otest" at /Developer/Tools/otest
2. Double click on the executable, and add two run params (requires Xcode 2.1):
a. -SenTest Self
b. $(BUILT_PRODUCTS_DIR)/YourBundleName.octest
This makes it REALLY easy to debug OCUnit tests. You can debug an individual test suite with:
-SenTest UnitTestClassName
or an individual test case with:
-SenTest UnitTestClassName/testMethodName
For instance, here is what one of my arguments looks like in Xcode 2.1:

I can easily test/debug all tests, or an individual test by turning on/off arguments. Note that it doesn't have the "$(BUILT_PRODUCTS_DIR)/YourBundleName.octest" option because this is for an executable, not a bundle.
Posted by corbin at 03:00 PM | Comments (0)
June 19, 2005
Drawing a “mail like” border on items in an NSTableView
Mail has a cool way of making unread messages stand out. It is really easy to do this type of thing with NSTableView/NSOutlineView. Subclass the one you want, and override drawRow. Toss in the code you see below, and it should give a cool highlight on all expandable rows in an NSOutlineView (you will have to modify it to have it work in NSTableView -- just remove the call to isExpandable).
- (void)drawRow:(int)row clipRect:(NSRect)clipRect {
if (([self isExpandable:[self itemAtRow:row]]) && (![self isRowSelected:row])) {
// Draw a light-blue “mail like” border around the row, if not selected
NSRect rect = [self rectOfRow:row];
[[[NSColor blueColor] colorWithAlphaComponent:50/255.0] set];
NSBezierPath *path = [NSBezierPath bezierPath];
[path setLineCapStyle:NSRoundLineCapStyle];
[path setLineWidth:rect.size.height - 3];
int rowLevel = [self levelForRow:row];
float x = rect.origin.x + 10.0 + (rowLevel * [self indentationPerLevel]);
float y = rect.origin.y + (rect.size.height / 2.0);
[path moveToPoint:NSMakePoint(x,y)];
[path lineToPoint:NSMakePoint(x + rect.size.width - 2*10.0, y)];
[path stroke];
}
[super drawRow:row clipRect:clipRect];
}
Posted by corbin at 02:37 PM | Comments (3)
June 17, 2005
Finding memory leaks with Object Alloc
So, your app leaks? Here's how to fix it:
1. Open your app in Object Alloc
2. Start the process, and check to have retain events:

3. Run till in a known state
4. Check "Show since mark":

5. Click auto-sort:

6. Click the "Current" column header to sort on that automatically:

7. Click the Mark button:

7. Do the offending memory leak operation (to warm it up)
8. Click Mark again, and repeat the offending memory leak operation.
9. Take a look at the Instance Browser, and find your objects that are leaked:

10. Pause the app, and in the right hand browser column will be the allocation events. Double click on them and you can see the stack of the allocation/retain/release event, and figure out who isn't "playing nice" by seeing who should have done a release for a corresponding alloc/retain/copy:

Have fun!
--corbin
Posted by corbin at 02:20 PM | Comments (1)
June 15, 2005
Changing the disclosure triangle in an NSOutlineView
At WWDC I was asked how to remove the disclosure triangle in an NSOutlineView. Well, first things first. You can change it with this bit of code in your delegate:
- (void)outlineView:(NSOutlineView *)ov
willDisplayOutlineCell:(NSButtonCell *)cell
forTableColumn:(NSTableColumn *)tableColumn
item:(id)item
{
[cell setImage:[NSImage imageNamed:@"collapsedglyph.tiff"]];
[cell setAlternateImage:[NSImage imageNamed:@"expandedglyph.tiff"]];
}
If you want to set it to nil, you will have to create an image that is empty.
--corbin
Posted by corbin at 05:47 PM | Comments (0)
June 10, 2005
Tooltips for NSTableView cell's in Tiger
At WWDC, I quickly mentioned how easy it is to add tooltip's to an NSCell for an NSTableView/NSOutlineView.
Here is a quick snippet of code on how to do this only if the text doesn't fill up the entire cell:
- (NSString *)tableView:(NSTableView *)tv toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc row:(int)row mouseLocation:(NSPoint)mouseLocation {
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
if ([[cell attributedStringValue] size].width > rect->size.width) {
return [cell stringValue];
}
}
return nil;
}
You will obviously have to set the delegate for the tableview to be whatever class implements the above method, and this will only work on Tiger. But, it is REALLY easy to do.
Technorati Tags: Cocoa
Posted by corbin at 03:32 PM | Comments (9)