Home / Paul's FAQs / Cocoa Programming FAQ

Cocoa Programming FAQ

Last Updated: 2003

These are the problems and solutions that I discovered while writing my first Cocoa program for Mac OS X. I hope you find this list useful! If you do, please let me know via E-mail.

Newbie FAQ:
-----------

** Basic Questions **

 1. What books do you recommend?
 2. I'm stuck! Where do I go for more information?
 3. Can I use my C++ classes in my Cocoa interface?
 4. Why do I get " illegal expression, found `unknown' " in perfectly good
    code?
 5. What's wrong with the documentation?  This thing should be documented,
    but I can't find it! (NSString, NSAttributedString, NSRect, etc.)

** Objective C and Memory management **

 6. Memory Management with Objective-C really gives me the willies.
 7. What does it mean when I get an EXC_BAD_ACCESS error?
 8. How do I check to see if I am leaking memory in Objective-C?
 9. Why doesn't the object I instantiated with InterfaceBuilder 
    ever receive a "dealloc" ?
10. I get malloc()/free() problems with 
    [NSData initWithBytesNoCopy: length:]
11. Can you have static variables inside an objective-C classes?
    OR: How do I share objects across my application?

** Interface Builder **

12. Interface Builder can no longer open my NIB file. Help!
13. Why don't my delegated functions get called?  Why don't my controls do
    anything?

** Controls and Buttons **

14. How do I hide controls?
15. I don't get how the springs work in the Interface-Builder control
    properties.
16. How do I set the focus to a control?
17. How do I set the "default button"
    (activated when the user presses RETURN), OR:  
    Why are buttons which were formerly default turning dark grey?

** Sheets and Alerts **

18. How do I display a simple MessageBox() / Alert Box ?
19. Why doesn't the Default button pulse when I use NSRunAlertPanel() ?
20. How do I show a sheet when my document window is first displayed ?
21. How do I show a sheet when my application is first launched ?
22. Why am I having problems using runModalForWindow with sheets?

** Drawing Strings **

23. How do I get the size of text if it were to be drawn on the screen?
24. How do I draw strings with different attributes (font, color,
    underline, italics, etc)?

** NSTextField and NSTextView **

25. How do I insert/append text into an NSTextField or NSTextView ?
26. Why can't I create a formatter to catch an empty NSTextField?
27. Why does my "textDidChange" method never get called for my NSTextField?
28. How do I get an NSTableView to do something on a double-click?
29. How do I disable an NSTableView?
    [myTable setEnabled:NO] doesn't seem to do anything.

** Folders and files in your Application **

30. How do I put a folder of files into my application?
31. And then how do I get access to them?

** Web sites, HTTP, and URLs ***

32. What is an informal protocol?
33. What is the NSURLClient informal protocol? How do I download a URL in
    the background?
34. How do I cancel an [NSURL loadResourceDataNotifyingClient:] in
    progress?
35. How do I launch a web browser to a URL?

** On-Line Help **

36. How do I add on-line help to my application?
37. How do I jump to a specific page of my on-line help?

** NSScanner **

38. Why can't I scan past newline or whitespace with NSScanner?


 1. What books do you recommend?
--------------------------------

I learned Cocoa and Objective-C using "Cocoa Programming for Mac OS X" by
Aaron Hillegass, and other Cocoa programmers seem to like it too.

Also try the books listed here: 
http://www.cocoadev.com/index.pl?CocoaBooks

The O'Reily book is also recommended.



 2. I'm stuck!  Where do I go for more information?
---------------------------------------------------

Start here:  http://cocoa.mamasam.com/

It is a **searchable** archive of the Cocoa Developer's mailing list.
Probably your issue has already been discussed on this list somewhere. A
good way to start is to search for the NS-class that you are having
trouble with.

If you want to become a member of the community (or just lurk), join
Apple's Cocoa Developers mailing list:

  http://www.lists.apple.com/mailman/listinfo/cocoa-dev

Ohter useful sites:

  http://www.cocoadev.com/
  http://www.cocoadevcentral.com/
  http://www.macdevcenter.com/



 3.  Can I use my C++ classes in my Cocoa interface?
----------------------------------------------------

Yes!  It works great.

Click here for more information on Objective-C++.



 4. Why do I get "illegal expression, found `unknown' " in perfectly good
    code?
--------------------------------------------------------------------------

Probably because you cut'd and paste'd the code from someplace with weird
end-of-line characters or other control characters. Try selecting the
white-space from just after the last character on the offending line to
just before the first character on the next line, and then hit
CARRIAGE-RETURN.

If that doesn't work, try the pull-down menu:  Format/Show Control
Characters.



 5. What's wrong with the documentation?  This thing should be documented,
    but I can't find it! (NSString, NSAttributedString, NSRect, etc.)
--------------------------------------------------------------------------

The documentation does have a few eccentricities:

A. Some classes are documented in both the Foundation Classes and the
Application classes documentation. These include:

  NSAttributedString, NSBundle, NSCoder, NSString,
  NSMutableAttributedString, NSURL

So, if you happend to be browsing the Application Classes documentation,
you will discover that these classes appear to be weirdly empty (for
example, no "length" method in NSAttributedString).


B. Many useful structures and functions are "hidden" at the bottom of the
Foundation classes API reference.


Click here and then scroll to the bottom of the page. and click on "Functions" where you will find many useful C functions (not methods), such as routines for NSDecimal, NSLog(), User names, Home directories, temporary directories, NSPoint manipulations, NSRanges (e.g. NSMakeRange()), NSRect manipulations, etc. Also at the end of the Foundation API reference you will find "Types and Constants". This is where most of the smaller C-structures are located, such as: NSRect, NSRange, NSDecimal, NSPoint, and NSSize. Note that these are structures and *not* Objective-C objects. C. There are also useful functions / structures hidden at the bottom of the APPKit reference:
Click here and then scroll to the bottom of the page. This includes useful functions such as counting windows, NSRunAlertPanel, NSBeep(), accessibility functions, etc. General Point: If you are frustrated with the documentation, take a step back and look at which API reference you are browsing. 6. Memory Management with Objective-C really gives me the willies. ------------------------------------------------------------------- You will be O.K. if you just keep these 'rules of thumb' in mind: A. If you got the object with "alloc" then be sure to "release" it: myobject = [[MyObject alloc] init]; // "alloc" has already done a "retain" on the object . . . - (void)dealloc { [myobject release]; [super dealloc]; } B. If you got the object with "alloc" and only need it temporarily, then just "autorelease" it: myobject = [[[MyObject alloc] init] autorelease]; // myobject will be automatically released when this itteration // of the event loop is complete C. If you got the object any other way (i.e. with one of the "+" calls), then the object has already been autoreleased. So, if the object is only used temporarily, then do nothing. It will be released automatically when the current itteration of the event loop is finished. If you want to keep the object, then be sure to "retain" it, and then "release" it in your dealloc method. For example: NSString *myString = [NSString stringWithString:@"Hello World!"]; // myString is AutoReleased by "stringWithString" and must be retained // if I plan to use it for a while: [myString retain]; . . . - (void)dealloc { [myString release]; [super dealloc]; } D. If you do a ton of stuff between event loops, it's possible that your application will use up a lot of memory from objects being "autoreleased" without actually being released. If this is the case, you should probably create your own autorelease pool, and then release it every so often. Click here for more details on creating your own autorelease pools. E. Don't forget to put a [super dealloc]; at the end of all your - dealloc routines! 7. What does it mean when I get an EXC_BAD_ACCESS error? --------------------------------------------------------- Probably you are using a bad pointer to an object. This is typically because: A. The object it once referred to has already been released: MyObject *myObject = [[MyObject alloc] init]; . . . [myObject release]; . . . [myObject myMethod]; // << Causes BAD_ACCESS error B. You have multiply autoreleased your object: MyObject *myObject = [[MyObject alloc] init]; [myObject autorelease]; . . . [myObject autorelease]; . . . // when the autorelease pool is cleaned up, your program will crash // with a BAD_ACCESS error, because it will attempt to release your // object twice C. You are dereferencing a bad 'C' pointer: myPtr = NULL; abc = myPtr->bTest; // << Causes BAD_ACCESS error Note that, unlike 'C', Objective C does allow you to send methods to 'nil' receivers: myObject = nil; [myObject doStuff]; // << No error, rather it is just ignored 8. How do I check to see if I am leaking memory? -------------------------------------------------- You could try using "ObjectAlloc" (an application delivered with the toolkit). This procedure seems to work fairly well: a. Launch your application with ObjectAlloc b. Run it for a while to "warm up" your memory. c. Swich back and forth between your application and the finder a few times d. Now, click on the X: all of the "current" settings go to zero. e. Execute the questionable code 5 times. f. Now click on "Show since mark" g. Look for allocations which are multiples of 5 and investigate them by looking at the Instance Browser or the Call Stacks. Warning: The foundation and application toolkits do lots of caching, and lots of times things are not released (as far as I can tell, example: bundles). So, don't sweat every little byte, especially when it is not allocated by your code. 9. Why doesn't the object I instantiated with InterfaceBuilder never receive a "dealloc" ? ---------------------------------------------------------------- I don't know. Probably you need to manually alloc, init, and dealloc your object with some sort of controller class, such as an application controller object. Bundles don't appear to be unloaded when you quit your program. If you have cleanup that you need to do on program termination, then have your obect listen for NSApplicationWillTerminateNotification (see #11 for an example). 10. I get malloc()/free() problems with [NSData initWithBytesNoCopy: length:] ----------------------------------------- Yes you do. The "bytes" field will be automatically free'd when using these routines: + (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length; - (id)initWithBytesNoCopy:(void *)bytes length:(unsigned)length; A future version of Cocoa (Jaguar?) will contain additional flags to control this behaviour: + (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length freeWhenDone:(BOOL)b; - (id)initWithBytesNoCopy:(void *)bytes length:(unsigned)length freeWhenDone:(BOOL)b; Until then, just use "initWithBytes". 11. Can you have static variables inside an objective-C classes? OR: How do I share objects across my application? ---------------------------------------------------------------- Basically, the best solution seems to be to define this global data as part of your @implementation, rather than inside the @interface. Here's a fairly complete solution, where the shared object is an instance of the "MyObject" class: A. Add the following method declarations to your MyObject.h @interface: + (MyObject *)getSharedObject(); - (void)doTerminate:(NSNotification *)note; B. Add the following to your MyObject.m @implementation: static MyObject *sharedMyObject = nil; // Points to the shared object + (MyObject *)getSharedObject() { if(sharedMyObject == nil) { sharedMyObject = [[MyObject alloc] init]; // Do other initializations of sharedMyObject here, as necessary [[NSNotificationCenter defaultCenter] addObserver:sharedMyObject selector:@selector(doTerminate:) name:NSApplicationWillTerminateNotification object:NSApp]; } return sharedMyObject; } - (void)doTerminate:(NSNotification *)note { if(sharedMyObject) { [[NSNotificationCenter defaultCenter] removeObserver:sharedMyObject]; // Do other cleanup on sharedMyObject here as necessary [sharedMyObject release]; } } Note: If you don't care that your shared object is released at program termination (or if you have other ways to properly release/dealloc it), then remove the NSNotificationCenter lines and the [ doTerminate:] method. 12. Interface Builder can no longer open my NIB file. Help! ----------------------------------------------------------- Did you recently delete or move things in your project? Move them back and then try opening your NIB file. For example, this will happen when the NIB contains an image, and you have moved the image (or moved it to the trash). 13. Why don't my delegated functions get called? Why don't my controls do anything? -------------------------------------------------------------------------- This can happen if you've made changes to your class include file, but have not propagated those changes into your NIB. Open up the NIB for your window, double click on "File's Owner", then execute the pull-down command: Classes / Read MyClass.h. And then go and verify all your connections. 14. How do I hide controls? --------------------------- Generally, your program will conform better with the OS X user interface guidelines if it disables the control rather than hiding it. However, if you really wish to hide a control, know that there is no "setVisible" method or attribute. Instead, you will need to remove the control with [myControl removeFromSuperview] to hide it and then add it back with [superviewOfMyControl addSubview]. There are some other issues, such as resizing and tab-order which you may also need to worry about. Here is one possible solution. A. Add the following files to your project: ----- ControlHider.h ----- #import <AppKit/AppKit.h> @interface ControlHider : NSObject { NSView *childView; // The child which is currently hidden NSSize oldFrameSize; // The parent's size when the control was removed NSView *parentView; // parent in which which held the child NSView *previousKeyView; // Pointer to child's previous key view } - (void)hideControl:(NSView *)child; - (void)showControl; @end ----- ControlHider.m ----- #import "ControlHider.h" @implementation ControlHider - (id)init { if(self = [super init]) { childView = nil; oldFrameSize.width = 0.0; oldFrameSize.height = 0.0; parentView = nil; previousKeyView = nil; } return self; } - (void)hideControl:(NSView *)child { NSRect rectParent = [[child superview] frame]; childView = child; parentView = [child superview]; oldFrameSize = rectParent.size; previousKeyView = [child previousKeyView]; [child retain]; [child removeFromSuperview]; } - (void)showControl { if(parentView == nil) return; [parentView addSubview:childView]; [previousKeyView setNextKeyView:childView]; [childView resizeWithOldSuperviewSize:oldFrameSize]; [childView release]; parentView = nil; } @end ----- end file ----- B. For each control which you wish to hide, add the following to your ".h" file: IBOutlet NSTextField *myControl; // the control to hide ControlHider *hiderMyControl; // << Add this line for each control C. In your "init" routine, allocate the ControlHider(s): hiderMyControl = [[ControlHider alloc] init]; D. In your "dealloc" routine, release the ControlHider(s): [hiderMyControl release]; E. Whenever you need to hide your control, do the following: [hiderMyControl hideControl:myControl]; F. Whenever you need to show your control, do the following: [hiderMyControl showControl]; Note: Do not "show" the control twice in a row. 15. I don't get how the springs work in the Interface-Builder control properties. --------------------------------------------------------------------- Actually, they're really cool, but they do seem to have a problem with controls which are outside their superview. Click on a control in the Interface-Builder, open up the properties (SHIFT-COMMAND-i), and then choose the "Size" pull-down menu, you will see a box called "Autosizing" (referred to as "Autoresizing" in the API documentation). Inside the "Autosizing" box there is a nested box and a bunch of double-lines. Click on a double line and it turns into a spring. There are six springs: A. Four springs between the edges of inner and outer box These affect the position of the box within its super view. The super view can either be the window/panel which holds the control or some nested view (such as an NSTabView or NSBox), which in turn holds the control. * When the spring is absent (i.e. just a straight line), the control will be "fixed" to the specified edge. This means that even if the view is resized, the control will maintain the same distance between itself and the fixed edge. * If there is a conflict, e.g. the control is fixed on all four sides and its superview changes size, the control will default to being fixed to the left and *bottom* of the view. B. Two springs inside the inner box (horizontal and vertical) These affect the size of the control as its super view is resized. * When a spring is absent, then the control will maintain a fixed size (either horizontal, vertical, or both) no matter how the superview is resized. * When a spring is present, the size of the control changes as the size of the superview changes. OK, so I don't understand how controls which are outside their superview work. In the Interface Builder you can drag a control outside it's superview (or expand the superview, add the control, and then collapse the superview). When automatically resizing the window with [myWindow setFrame:newFrame display:YES animate:YES] it seems that the control automatically "pops" into view before being moved down. Others have reported that similar strange things happen when drawers open up from the sides of windows. However, it seems to work OK when the user manually resizes the window. I guess just watch out when you have controls that are outside of their superview. Sometimes the behaviour will be strange and you may need to move the controls yourself with [myControl setFrame:newFrame] and [myControl dispay] commands. 16. How do I set the focus to a control? ---------------------------------------- Use "makeFirstResponder": [myWindow makeFirstResponder:myControl]; 17. How do I set the "default button" (activated when the user presses RETURN), OR: My buttons which used to be default turn dark grey. ---------------------------------------------------------------------- There are two ways to set the "default" button, A. [myButton setKeyEquivalent:@"\r"]; B. [myWindow setDefaultButtonCell:[myButton cell]]; Version 'B' is better, since, with Version A, if you turn off the keyEquivalent (i.e. set it to the empty string or something) then the button will end up a dark-grey color, depending on how dark the blue pulsing was when the switch occurred. 18. How do I display a simple MessageBox() / Alert Box ? --------------------------------------------------------- Use NSRunAlertPanel() or NSRunCriticalAlertPanel(). See: Click here for more information on NSRunAlertPanel() For example: int iResponse = NSRunAlertPanel(@"Close Document", @"Are you sure you want to close the document?", @"OK", @"Cancel", /*ThirdButtonHere:*/nil /*, args for a printf-style msg go here */); switch(iResponse) { case NSAlertDefaultReturn: /* user pressed OK */ break; case NSAlertAlternateReturn: /* user pressed Cancel */ break; case NSAlertOtherReturn: /* user pressed the third button */ break; case NSAlertErrorReturn: /* an error occurred */ break; } If your application is not currently the foremost application, the user will be notified (bouncing icon) that it has posted an alert and requires attention. 19. Why doesn't the Default button pulse when I use NSRunAlertPanel() ? ------------------------------------------------------------------------ This is a known bug. 20. How do I show a sheet when my document window is first displayed ? ----------------------------------------------------------------------- The problem here is that your application must be fully initialized before you can run your own sheets. Therefore, you can not launch your panel in [NSDocument windowControllerDidLoadNib:...] or [object awakeFromNib:...] or similar methods because the application is not fully initialized yet. If the application is not fully initialized, some weird things can happen, such as missing controls, strange titles, etc. Also, you may notice that your sheet looks fine (and works fine, too), but takes an extra click to activate it (i.e. an extra click before it becomes the "key" window). So, one possible solution is to send a message the first time the window becomes Key (because that's when the window object is available), and to use the [NSRunLoop performSelector:] method to delay the message by one event loop (gives the application time to start up). Steps to implement this solution: A. Have one of your objects become the delegate of the first window which is launched. For example, your NSDocument could be the delegate for your main document window. B. Then, handle the "windowDidBecomeKey" delegated function. This will be called after the document window is popped to the front: - (void)windowDidBecomeKey:(NSNotification *)aNotification { if(!mySheetController) { // only executed the first time the window becomes Key mySheetController = [[MySheetController alloc] init]; [mySheetController setDocWindow:window]; //* Hack to send startup sheet when application is ready [[NSRunLoop currentRunLoop] performSelector:@selector(showWindow:) target:mySheetController argument:self order:0 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; } } C. Then, add the following code to your "MySheetController" object (the object which handles the buttons on your sheet): // Set the parent window so it can be used in [NSApp beginSheet:...] - (void)setDocWindow:(NSWindow *)window { docWindow = window; } - (IBAction)showWindow:(id)sender { // Perform initializations on your sheet needed before it is displayed . . . [NSApp beginSheet:panelChoose modalForWindow:docWindow modalDelegate:sender didEndSelector:@selector(myDidEnd:returnCode:contextInfo:) contextInfo:nil]; } D. Notice that "NSApp beginSheet" has specified a modalDelegate: which will receive the "DidEnd:" message when the sheet is closed. To handle this be sure to add the following method to the object which is the delegatee: - (IBAction)myDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if(returnCode) /* Do something if the user clicked OK */; else /* Do something if the user clicked Cancel */; } E. Don't forget to shut down your sheet properly with [NSApp endSheet:returnCode:] (see #22 below). 21. How do I show a sheet when my application is first launched ? ------------------------------------------------------------------ If you want to display a sheet whenever you open a new document, then use #20 above. If you only need to show the sheet once when your program is launched, then register your object to listen for NSApplicationDidFinishLaunchingNotification and display the sheet there. 22. Why am I having problems using runModalForWindow with sheets? ----------------------------------------------------------------- Because runModalForWindow is "application modal", not just "window modal". Application modal means that the dialog takes over application event processing, so none of the application menus work, window events don't get processed, etc. Window modal continues all standard event processing. You can open new documents and use the other menu commands in your application. The only difference, really, is that events to the window will be sent to the sheet instead of the window. Sheets are intended to be Window Modal, not application modal, and so they don't work well with "runModalForWindow". If you do, then strange things may happen with your window, such as the default button not throbbing or (as has been reported a ways back) old sheets previously displayed may magically pop back up. Suppose this is what you had in your code: - (void)doMySheet { int response; [NSApp beginSheet:mySheetWindow modalForWindow:myDocumentWindow modalDelegate:nil didEndSelector:nil contextInfo:nil]; // >>> Wrong: don't use runModalForWindow with sheets <<< response = [NSApp runModalForWindow:mySheetWindow]; [NSApp endSheet:mySheetWindow]; [mySheetWindow orderOut:self]; if(response == 0) return; // continue with application } Change it to this: - (void)doMySheet { int response; [NSApp beginSheet:mySheetWindow modalForWindow:myDocumentWindow modalDelegate:self didEndSelector:@selector(mySheetDidEnd:returnCode:contextInfo:) contextInfo: nil]; return; // leave without doing anything else } - (IBAction) mySheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo [sheet orderOut:self]; if(returnCode == 0) return; // continue with application } (I know: if you have a lot of local variables in doMySheet it's a pain, sorry 'bout that) And don't forget to do these things as well: A. Be sure to declare mySheetDidEnd in the appropriate .h file B. Instead of using [NSApp stopModal] to end your dialog, use [NSApp endSheet:window returnCode:1]; 23. How do I get the size of text when it is drawn on the screen? ------------------------------------------------------------------ First, you need to get an "attributed" string (see NSAttributedString in the application toolkit), then you can call [myAttributedString size] to get the height and width of the string as drawn on the screen. You can get an attributed string directly from controls with [myControl attributedStringValue]. An "AttributedString" is simply a string with a set of attributes. Attributes are stored in a dictionary (NSDictionary or NSMutableDictionary). Attributes are name/value pairs stored in the dictionary which specify things like Font Name, Underlined, Foreground Color, Background Color, etc. So, to create your own attributed strings, you need to create a dictionary with name/value pairs for your text attributes. A list of constants for possible attribute names (such as NSFontAttributeName) can be found here: A href="http://developer.apple.com/techpubs/macosx/Cocoa/Reference/ApplicationKit/ObjC_classic/Classes/NSAttributedString.html">Click here for constants and attribute names. To create a dictionary: (example) NSDictionary attribDict = [NSDictionary dictionaryWithObjectsAndKeys:         [NSColor redColor], NSForegroundColorAttributeName,          [NSFont systemFontOfSize:24], NSFontAttributeName, nil]; To create the attributed string: NSAttributedString *attribString = [[NSAttributedString alloc] initWithString:@"This is my string" attributes:attribDict]; And now you can get the size of the string with: NSSize textSize = [attribString size]; 24. How do I draw strings with different attributes (font, color, underline, italics, etc)? ------------------------------------------------------------------- See #23 above about creating attributed strings. Once you have an attributed string, use the "drawAtPoint" and "drawInRect" methods to draw the string. 25. How do I insert/append text into an NSTextField or NSTextView ? ------------------------------------------------------------------- If you want to replace the currently selected text of an NSTextField, OR insert text at the current insertion point, use the following code: NSText *textEditor = [myTextField currentEditor]; [textEditor replaceCharactersInRange:[textEditor selectedRange] withString:@"hello world"]; The deal here is that NSTextField shares the NSText object with all of the other NSTextField's in your window, so you need to get the current editor first and then you can send it commands to modify the contents of your NSTextView. Also note that this will not work unless the control is currently active (i.e. the first responder, see #16). If you want to insert text into an NSTextField without making it active, you could: A. Get the text, with [myControl stringValue], append to the string, and then set it again with [myControl setStringValue]. B. Change the NSTextField to an NSTextView, and then modify the NSTextStorage of the NSTextView directly. For example: // This will append text to the end of an NSTextView: NSRange myRange = NSMakeRange([[myTextView textStorage] length], 0); [[myTextView textStorage] replaceCharactersInRange:myRange withString:@"Text to append to the end"]; By doing this you will be allocating a dedicated text editor to your field. 26. Why can't I create a formatter to catch an empty NSTextField? ----------------------------------------------------------------- Because the formatter does not get called when the user attempts to leave a blank NSTextField. Instead, add this delegate function to the object which manages your window: - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { if (control == myControl) { if ([[fieldEditor string] isEqualToString:@""]) { NSBeep(); return NO; } } return YES; } ... and make sure to make the object the delegate of your *control* in Interface Builder. If the field is blank, the user can enter it and leave it at will (i.e. with TAB or mouse clicks or whatever). However, if the user enters something, clears it, and then attempts to leave, this function will be called and will cause a system beep. I have not yet figured out how to prohibit the user from leaving an empty NSTextField if they jump into it and then jump right out. Maybe that's not a good user interface technique anyway. Helpful hint: Instead of beeping when the user enters an empty text field, why not just enable/disable the OK button? In other words, disable the OK button until all of your requred Text Fields have been entered. You can trap the delegated method "controlTextDidChange", then see if all required fields have something in them, and then enable/disable the OK button. 27. Why does my "textDidChange" method never get called for my NSTextField? --------------------------------------------------------------------------- Three possible reasons: A. It should be "controlTextDidChange" (see NSControl), not simply "textDidChange". B. You forgot to make your object (i.e. the object in which controlTextDidChange is defined) the delegate for your control in the Interface Builder. C. See #13. 28. How do I get an NSTableView to do something on a double-click? ------------------------------------------------------------------ Put this code someplace where you do initializations: [myTableView setTarget:myWindowController]; [myTableView setDoubleAction:@selector(doDoubleClick:)]; And add this function to your .m file (don't forget to declare it in your .h include file): - (IBAction)doDoubleClick:(id)sender { // Do what you want to do, sender points to the table view } 29. How do I disable an NSTableView? [myTable setEnabled:NO] doesn't seem to do anything. -------------------------------------------------------- NSTableView does not appear to respond to setEnabled:. The following code is a sub-class of NSTableView which adds enabling and disabling. Steps: A. Add the following files to your project: ----- ExtendedTableView.h ----- #import <AppKit/AppKit.h> @interface ExtendedTableView : NSTableView { NSMutableArray *saveTextColors; NSMutableArray *saveBackgroundColors; BOOL saveVerticalScrollerEnabled, saveHorizontalScrollerEnabled; } - (void)saveState; - (void)setEnabled:(BOOL)bEnable; @end ----- ExtendedTableView.m ----- #import <AppKit/AppKit.h> #import "ExtendedTableView.h" @implementation ExtendedTableView - (BOOL)acceptsFirstResponder { return [self isEnabled]; } - (void)dealloc { [saveBackgroundColors release]; [saveTextColors release]; [super dealloc]; } - (void)saveState { NSEnumerator *e = [[self tableColumns] objectEnumerator]; NSTableColumn *curColumn; if(saveBackgroundColors == nil) saveBackgroundColors = [[NSMutableArray alloc] init]; if(saveTextColors == nil) saveTextColors = [[NSMutableArray alloc] init]; [saveTextColors removeAllObjects]; [saveBackgroundColors removeAllObjects]; while ((curColumn = [e nextObject])) { if([[curColumn dataCell] isKindOfClass:[NSTextFieldCell class]]) { [saveTextColors addObject:[[curColumn dataCell] textColor]]; [saveBackgroundColors addObject:[[curColumn dataCell] backgroundColor]]; } } saveVerticalScrollerEnabled = FALSE; saveHorizontalScrollerEnabled = FALSE; if([[self enclosingScrollView] hasVerticalScroller]) saveVerticalScrollerEnabled = [[[self enclosingScrollView] verticalScroller] isEnabled]; if([[self enclosingScrollView] hasHorizontalScroller]) saveHorizontalScrollerEnabled = [[[self enclosingScrollView] horizontalScroller] isEnabled]; } - (void)setEnabled:(BOOL)bEnable { NSEnumerator *ec; NSEnumerator *etc = nil, *ebc = nil; NSTableColumn *curColumn; NSColor *textColor = nil, *backgroundColor = nil; if([self isEnabled] == bEnable) return; [super setEnabled:bEnable]; ec = [[self tableColumns] objectEnumerator]; if(!bEnable) { [self saveState]; textColor = [NSColor colorWithCalibratedWhite:0.50 alpha:1.0]; backgroundColor = [NSColor colorWithCalibratedWhite:0.94 alpha:1.0]; } else { etc = [saveTextColors objectEnumerator]; ebc = [saveBackgroundColors objectEnumerator]; } while ((curColumn = [ec nextObject])) { if([[curColumn dataCell] isKindOfClass:[NSTextFieldCell class]]) { if(bEnable && etc && ebc) { textColor = [etc nextObject]; backgroundColor = [ebc nextObject]; } if(textColor) [[curColumn dataCell] setTextColor:textColor]; if(backgroundColor) [[curColumn dataCell] setBackgroundColor:backgroundColor]; } } if([[self enclosingScrollView] hasVerticalScroller]) [[[self enclosingScrollView] verticalScroller] setEnabled:(bEnable && saveVerticalScrollerEnabled)]; if([[self enclosingScrollView] hasHorizontalScroller]) [[[self enclosingScrollView] horizontalScroller] setEnabled:(bEnable && saveHorizontalScrollerEnabled)]; [self setNeedsDisplay:YES]; } @end ----- End Files ----- B. Change your IBOutlet(s) from NSTextView to ExtendedTextView for all of the text views to which you want to add Enabling capability. C. Open the NIB file for your window. Drag a copy of ExtendedTextView.h from your project into the Interface Builder window (to let IB know about the new sub-class). D. In Interface Builder, change the class for your NSTextView(s) to ExtendedTextView. If the "CustomClass" of the control only says "NSScrollView", then double-click on the TableView. You should then be able to set the class to ExtendedTextView. E. Make your window controller (or some other object) the delegate for your table view(s). Then implement these delegated functions: - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView { return [aTableView isEnabled]; } - (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { return [aTableView isEnabled]; } - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(int)rowIndex { return [aTableView isEnabled]; } - (BOOL)tableView:(NSTableView *)aTableView shouldSelectTableColumn:(NSTableColumn *)aTableColumn { return [aTableView isEnabled]; } // (don't forget to add the declarations for these functions to your // .h include file) And now you should be able to call [myExtendedTableView setEnabled:NO] and have the view actually be disabled. Note 1: The code above only works for tables that made up of NSTextFieldCell's. If you have other types of cells in your table, you will need to add them to the code above. Note 2: Do not add or remove columns to the table when it is disabled, or otherwise the colors will probably get screwed up when it is re-enabled. 30. How do I put a folder of files into my application? ------------------------------------------------------- By now you have probably figured out that applications in Mac OS X are actually folders full of information such as the executable, NIB files, etc. This is a totally cool concept because it also allows you to hide your own stuff (database files, help files, etc.) inside an application. To include a folder with files in your application, drag the folder into the project. A sheet will pop-down. Check "Create Folder References for any added folders". The folders (and the files within them) will now be copied to your application when you build your project. Note that the folder is blue (instead of tan/yellow). 31. And then how do I get access to them? ----------------------------------------- That's easy too, do this: NSArray *myArray = [[NSBundle mainBundle] pathsForResourcesOfType:nil inDirectory:@"MyFolder"]; to get a list of all the files (an array of NSString(s) which are full path names) in your folder, or this: NSString *myFilePath = [[NSBundle mainBundle] pathForResource:@"MyFile" ofType:@"ext" inDirectory:@"MyFolder"]; to get the full path name of a single file. Once you have the full path name, you can open the files with ordinary File I/O routines. 32. What is an informal protocol? --------------------------------- Basically, it is a protocol that you don't have to define with <TheProtocol> in your Objective-C class @interface specification. The advantages are that it's easy to use. Just declare the methods you need from the informal protocol in your class and then implement them. The disadvantage is that others can't tell whether your class has implemented the protocol or not. Click here for more information on informal protocols. 33. What is the NSURLClient informal protocol? How do I download a URL in the background? ------------------------------------------------------------------------- Here is the NSURLClient informal protocol: (from the Mac OS X Developer Release Notes) @interface NSObject(NSURLClient) - (void)URL:(NSURL *)sender resourceDataDidBecomeAvailable:(NSData *)newBytes; - (void)URLResourceDidFinishLoading:(NSURL *)sender; - (void)URLResourceDidCancelLoading:(NSURL *)sender; - (void)URL:(NSURL *)sender resourceDidFailLoadingWithReason:(NSString *)reason; @end The purpose of the class is to allow you to easily download URLs in the background. Just implement the methods (in any object you like) that you want. The steps to download a URL in the background are: A. Create an NSURL object with the URL you want to load. B. Execute the [myURL loadResourceDataNotifyingClient:usingCache:] method. Provide some object of yours as the "client" to this call. C. In your client object, implement whichever classes from the NSURLClient informal protocol that you would like. In particular, to get the contents of the file you will likely need to implement this method: - (void)URLResourceDidFinishLoading:(NSURL *)sender { NSData *dataContents = [sender resourceDataUsingCache:YES]; [dataContents writeToFile:@"myfile.html" atomically:YES]; // (or do whatever you want to do with the URL data) } 34. How do I cancel an [NSURL loadResourceDataNotifyingClient:] in progress? ------------------------------------------------------------------ You can't. Instead, you need to use NSURLHandle. But don't worry: it's easy, just follow these steps: A. Decide which of your classes will receive notifications from NSURLHandle and add the <NSURLHandleClient> implements-protocol specifier to it's @interface line, for example: @interface MyInternetController : NSWindowController<NSURLHandleClient> { . . . } @end B. Implement whichever of these routines in your class you want: - (void)URLHandleResourceDidBeginLoading:(NSURLHandle *)sender; - (void)URLHandleResourceDidCancelLoading:(NSURLHandle *)sender; - (void)URLHandleResourceDidFinishLoading:(NSURLHandle *)sender; - (void)URLHandle:(NSURLHandle *)sender resourceDataDidBecomeAvailable:(NSData *)newBytes; - (void)URLHandle:(NSURLHandle *)sender resourceDidFailLoadingWithReason:(NSString *)reason; Watch out: Sometimes if there is a fast error, it appears that URLHandleResourceDidBeginLoading can actually occur *after* resourceDidFailLoadingWithReason. C. Inside the URLHandleResourceDidFinishLoading: method, you can access the data downloaded with: NSData *myData = [sender resourceData]; (see #33 above). E. To start the download, put this code into your class and call it: - (void)download { nsURL = [[NSURL URLWithString:urlString] retain]; urlHandle = [[nsURL URLHandleUsingCache:NO] retain]; [urlHandle addClient:self]; [urlHandle loadInBackground]; } // Don't forget to declare "nsURL" and "urlHandle" in your class and // also to "release" them somewhere F. If you need to cancel the download, then do this: [urlHandle cancelLoadInBackground]; 35. How do I launch a web browser to a URL? ------------------------------------------- Use NSWorkspace: BOOL isOK = [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http:// ... "]]; 36. How do I add on-line help to my application? ------------------------------------------------ Additional documentation for on-line help can be found in: "/Developer/Documentation/Apple Help/" Basically, the steps are as follows: A. Create your help as a folder of HTML files and image files (as necessary). Your HTML files can have frames, tables... actually most anything (there are also Apple extensions for launching programs and running AppleScript). B. Drag your folder into your ProjectBuilder window, probably under "Resources" is best. A sheet will slide-down. Check "Create Folder References for any added folders". The folders (and the files within them) will now be copied to your application when you build your project. Note that the folder is blue (instead of tan/yellow). C. Choose one of your HTML pages to be the primary entry point. In order for the Help system to locate this page, put a <META NAME="AppleTitle"...> tag inside this file, like this: <HEAD> <META NAME="AppleTitle" CONTENT="MyApplication Name"> <TITLE>My Program Title</TITLE> </HEAD> D. Under the "Project" pull-down menu, choose "Edit Active Target". E. Click on the "Application Settings" tab, and then on "Expert". F. Add these two new properties to your target: Property Class Value ---------------------- ------ ----- CFBundleHelpBookName String MyApplication Name CFBundleHelpBookFolder String MyApplication Folder CFBundleHelpBookName should be the exact same string you specified in the <META> tag. CFBundleHelpBookFolder should be the exact same string you specified for the folder which contains all of your HTML files. G. In your MainMenu.nib (Interface Builder), edit the menu so that under "Help" it says "MyApplication Help". Also, this menu command should be connected to the "showHelp:" command of the FirstResponder obejct (turns out this is ultimately handled by NSApplication). Troubleshooting: * If you get an error message which says "Help isn't available for MyApplication", then probably the name of your folder doesn't match the CFBundleHelpBookFolder, or your folder was improperly included into your project (it's yellow, not blue), or it's in the wrong place, or something like that. * If, when you launch help, you go directly to the "Help Center" instead to your main application, then you will probably see your application's help in the list somewhere. This could occur for several reasons: 1) the name for CFBundleHelpBookName does not match the <META> tag in your primary help file, 2) you have multiple HTML files in your folder and more than one of them specify a <META NAME="AppleTitle"...> tag, or 3) there are HTML syntax errors in your file (i.e. missing <HTML> tags, missing angle-brackets, etc). 37. How do I jump to a specific page of my on-line help? -------------------------------------------------------- You need to use functions available in the Carbon APIs to do this, specifically "AHGotoPage()". Fortunately, including them in your project is pretty easy. Follow these steps: A. Put the following files in your project: ----- HelpPage.h ----- #import <AppKit/AppKit.h> #import <Carbon/Carbon.h> void OpenHelpPage(NSString *page); ----- HelpPage.m ----- #include "HelpPage.h" void OpenHelpPage(NSString *page) { NSString *helpBookName = [[[ NSBundle mainBundle ] infoDictionary ] objectForKey:@"CFBundleHelpBookName" ] ; AHGotoPage( (CFStringRef)helpBookName, (CFStringRef)page, nil ); } ----- End ----- B. Add the Carbon framework to your project. Execute the pull-down menu comment: Project / Add Frameworks..., click on "Carbon.framework" and then click "Open". C. Wherever you need to use the new function, import the "HelpPage.h" include file. D. Then make calls as follows: OpenHelpPage(@"mypage.html"); Will open the "mypage.html" file located in your Help folder. 38. Why can't I scan past \n with NSScanner? -------------------------------------------- Because it is in the "characters to be skipped" list. For example, the following code: NSScanner *myScanner; myScanner = [NSScanner scannerWithString:@"\nThis is a string"]; [myScanner scanCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@" \r\t\n"] intoString:nil]; // > The scanner is now positioned on the "\n" character, // not the "T" of "This" To fix this issue, add the following line to your code: [myScanner setCharactersToBeSkipped:nil];