Mac OS X Programming Tips

Japanese version is here.

This page contains tips and tricks I've found while developing Cocoa Browser.

Contents:


Programming Tips from Cocoa Browser ver 0.1

(Mis?)Usage of Document-based Application

Through Cocoa Browser is not a normal document-based application, I've chosen it as the template to be able to open multiple windows.

As a result, Cocoa Browser does nothing on "Open...", "Save", etc. I'm sorry for this confusion.

Layout of NSTextView

Layouting NSTextView to the same size as the window, scrollbar's position doesn't align with the size box. To fix this, enlarge the frame rectangle to left, right, and bottom by 1 pixel each.

But in Cocoa Browser, NSTextView resides in NSSplitView which doesn't allow subviews to have arbitrary frame rectangle. Adding a dummy NSView between them solves the problem.

Processing NSBrowser

To display the contents of NSBrowser, add the following two methods.

    - (int) browser: (NSBrowser *) sender numberOfRowsInColumn: (int) column;
    - (void) browser: (NSBrowser *) sender willDisplayCell: (id) cell atRow: (int) row column: (int) column;

To do something when NSBrowser's is clicked, add an action method and set the target and action. (Also, to do something different when double clicked, call - setDoubleAction: in - awakeFromNil. It can't be done with Interface Builder...)

Notice the order of these calls. NSBrowser updates the cells before calling the action method, therefore, - browser:numberOfRowsInColumn: etc. are called before the action method.

Display HTML document in NSTextView

It's very easy to display HTML document in NSTextView.

    NSData *data = < HTML data >;
    NSURL *url = < base URL >;
    NSAttributedString *attrString = [[NSAttributedString alloc]
	    initWithHTML: data baseURL: url documentAttributes: (NSDictionary **) NULL];
    [[textView textStorage] setAttributedString: attrString];
    [attrString release];

NSTextView also displays the inline images. (Base URL is used to resolve the location of the images specified with relative paths.)

HTML data need not to be a full HTML, i.e., Cocoa Browser splits the original HTML document into small pieces and display one.

Managing .nib and .pbproj files with CVS

You don't need to set CVSROOT environment variable. See cvs -d. It would be useful especially when you have several repositories.

Put cvswrappers into CVSROOT/cvswrappers in the repository, instead of ~/.cvswrappers, to have different setting for each repository. Apple provides a sample in /Developer/Tools/cvswrappers, which enables .nib files to be managed with Project Builder.

I've modified it to manage .pbproj files, but it can't be diffed, and managed with Project Builder. Have I made a mistake?

Correction: I should have added "project.pbxproj" file in the .pbproj directory. It can be diffed (in Terminal), and my user preferences are ignored.

Programming Tips from Cocoa Browser ver 0.2

Detect mouse click on the link

When you click on a link, NSTextView sends - textView:clickOnLink:atIndex: message to its delegate. In Cocoa Browser, internal links are processed and it return YES. For external links, it returns NO and Application Kit performs the default action -- tell the default web browser to open the link.

(I don't know how to change the mouse cursor when it's on a link. By the way, should internal and external links to be displayed in different colors?)

Adding menu items and auto-enabling

To add menu items in multiple .nib, add methods to the first responder class, and connect to its instance.

To enable/disable menu items according to the situation, implement - validateMenuItem: method.

Unselect cells in NSBrowser

How to unselect cells in NSBrowser? - selectRow:inColumn: doesn't work.

I've found that emptying NSBrowser once and reverting seems to work. i.e., add a flag, modify - browser:numberOfRowsInColumn: to retun 0 if the flag is not normal, and

    < flag = not normal >;
    [browser reloadColumn: 0];
    < flag = normal >;
    [browser reloadColumn: 0];
Correction: Simply sending - loadColumnZero does the trick.

Enlarge/shrink NSTextView

Some people said "fonts are too small." Yes, I know it's small. But it's NSAttributedString's default behaviour. Two ideas came to mind: adding <font size="+1"> tag, and sending - modifyFont: with NSSizeUpFontAction to NSFontManager. Both seems to be troublesome, and neither is perfect.

Finally, I've found that NSView and any of its subclass can be scaled by - setBoundsSize: method. But, NSRunStorage throws exception when NSTextView's contents are changed. To fix this, add

    [textView setString: @""];

before

    [[textView textStorage] setAttributedString: attrString];

(Scroll position is changed by scaling. To fix this, - characterIndexForPoint: and - scrollRangeToVisible: seems to work. However, I don't know where to pass - characterIndexForPoint:. By the way, should NSBrowser's font be scaled?)

How to compile Java version

Cocoa Browser is open source. To compile, extract source files from src.tar.gz, open Cocoa Browser.pbproj, and compile.

Cocoa Browser ver 0.2 has a separate application to browse the Java version of the reference document. To compile as the Java version, delete #define LANGUAGE_OBJECTIVE_C from ClassNode.m.


Programming Tips from Cocoa Browser ver 0.3

Size of Deployment binary

Deployment binary was huge! I've found a solution in Witness of Teachtext: Tips & Tricks.

Key presses in NSBrowser

Max Horn taught me how to accept key events in NSBrowser; add the following method to the delegate.

    - (BOOL) _browser: (NSBrowser *) sender
	    keyEvent: (NSEvent *) event inColumn: (int) column;

Because this is an undocumented API, it may not work on future versions of Mac OS X.

NSSlider to update NSTextField

To update NSTextField while dragging NSSlider, connect them and set the action to - takeIntValueFrom:. Easy.

However, sometimes NSTextField fails to update. To improve this situation, send message to MyDocument and check if the value actually changed. Though it improves a bit, problem still remains...

Link's cursor and external links

In Mac OS X Dev-jp Mailing List (in Japanese), Mr. Ishikawa (in Japanese) taught me how to change link's cursor.

Using that method, I also changed the color of external links, and preserve scroll position on Magnify and Go Back and Forward.

To implement it, I created a subclass of NSTextView. But, Interface Builder doesn't allow me to specify the subclass. I don't like to put a CustomView and set the subclass, as I want to set the properties in Interface Builder, not by source code.

So, I added a method to replace NSTextView with the subclass and copy the properties, based on Aaron Hillegass' excellent book Cocoa Programming for Mac OS X.

Toolbar

I've followed /Developer/Examples/AppKit/SimpleToolbar to add the toolbar. (Most difficult task was, painting arrow icons!)

By the way, I don't like the way of implementation and API design of Toolbar in Cocoa. Do you?

Addendum: I'm very glad that I can have small icons in Toolbar on Mac OS X 10.2.

Programming Tips from Cocoa Browser ver 0.4

Document-based Application revised

When I tried to remove '????' from document types, "New" and "Open..." stop working, and no window appears on launch.

To fix this, add delegate to NSApp and subclass of NSDocumentController.

Preserve window position and status

I already use - setFrameAutosaveName: to preserve the window position of Find dialog, but it doesn't work for main windows because it works for single window only.

I also want to preserve the magnification and text contents for each window.

So, I generate an array of dictionaries of window status and save into user defaults. I use - stringWithSavedFrame and - setFrameFromString: methods to save and restore window position.

Separate .nib file of Magnify sheet

I separate MagnifyWindow.nib from MyDocument.nib to make .nib files simple and lower memory usage.

I also separate MagnifyController class to simplify MyDocument class a bit.

But, MyDocument class is still crowded. How can I improve it?

Refactor Node classes

Node and ClassNode classes have ad-hoc design, and lacks expandability.

So I split ClassNode class into NodeWithFile and NodeParser classes. It will ease to support more documents, I hope.

Mac OS X 10.2

Cocoa Browser had some problems on Mac OS X 10.2 (Jaguar).

First, a relative link stop working, even though I set the baseURL.

Second, "By" in "Methods Implemented By the Delegate" has been changed to "by", breaking "Delegate Methods". Thanks to Andy Lee (author of AppKiDo) for telling me about this.


Hoshi Takanori