Adding Items to NSTextView’s Contextual Menu

When you add a text view to your application using Interface Builder you get access to the text view’s default contextual menu. You can open the contextual menu in your application by right-clicking (or control-clicking) in the text view. This menu lets you do things like cut, copy, paste, and check the document for spelling errors. If you’ve read Aaron Hillegass’s Cocoa programming book you know how to replace the default contextual menu, but you may want to use the default menu and add some menu items to it. If you’re writing an HTML editor, you may want to add a menu item to wrap HTML tags around selected text. This post shows you how to add a menu item to NSTextView’s contextual menu.

Accessing the Contextual Menu

The first step to adding an item to a text view’s contextual menu is to access the menu. You can access NSTextView’s default contextual menu using the menu: method.

IBOutlet NSTextView* textView;
NSMenu* textViewContextualMenu = [textView menu];

Adding a Menu Item to the Contextual Menu

After you get access to the contextual menu, call NSMenu’s addItemWithTitle: method to add a menu item to the contextual menu. You must supply three pieces of information: the name of the menu item, the action, and the keyboard equivalent for the menu item. The action is the method that gets called when the user chooses the menu item. The following example adds a Tags menu item to the contextual menu with no keyboard equivalent:

- (IBAction)createTag:(id)sender;
NSMenuItem* tagsMenuItem = [textViewContextualMenu addItemWithTitle:@"Tags" action:@selector(createTag:) keyEquivalent:@""];

If you want to place a menu item in a specific place in the contextual menu, call NSMenu’s insertItemWithTitle: method. This method works similarly to the addItemWithTitle: method, but there is an additional argument to specify: the index (location in the menu) where you want to insert the menu item.

Adding a Submenu

Sometimes you need to add a submenu to a menu item. In the example I’ve been using in this article, you may decide to add a menu of tags as a submenu of the Tags menu so the user can add a specific tag. Call the contextual menu’s setSubmenu: method to add a submenu. Supply the menu to add and the menu item where you’re adding the menu. The following example adds a menu of tags to the Tags menu item:

IBOutlet NSMenu* tagsMenu;
[textViewContextualMenu setSubmenu:tagsMenu forItem:tagsMenuItem];

The example assumes you created a menu in Interface Builder. Add a NSMenu object to the xib file, and add items to the NSMenu object. Creating a menu in Interface Builder is generally easier than creating a menu programmatically, but you can create the menu in your source code if you want. Refer to the NSMenu Class Reference, which is part of Apple’s documentation, to learn more about creating a menu in code. Searching for NSMenu in Xcode’s documentation window should be enough to find the class reference.

No comments | Trackback

Saving a Property List in a File Wrapper

Saving a property list in a file wrapper takes two steps. First, call NSPropertyListSerialization’s dataFromPropertyList: or dataWithPropertyList: methods to create a property list. The dataWithPropertyList: method was introduced in Mac OS X 10.6. Use dataFromPropertyList: if you are supporting earlier versions of Mac OS X.

NSMutableDictionary* root;
NSError* error;
NSData* propertyList = [NSPropertyListSerialization dataWithPropertyList:root format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
Second, pass the property list you created as the first argument to NSFileWrapper's addRegularFileWithContents: method.
NSFileWrapper* wrapper;
[wrapper addRegularFileWithContents:propertyList preferredFilename:@"MyFile.plist"];
My Working with Cocoa File Packages post has detailed information on file packages. Read Apple's Property List Programming Guide for more information on property lists.
No comments | Trackback

Loading Files for Unit Testing in Cocoa

Adding files to your unit testing bundle helps when you’re unit testing file behavior or when you have a lot of test data. This post shows you how to load files from the unit testing bundle to help you unit test Cocoa applications.

Before You Code

The first thing you must do is get your test files in the unit testing bundle. Add the test files to your project. When adding the files, add them to the unit testing target, not the application target. Make sure the test files are part of the unit testing target’s Copy Bundle Resources build phase. When you build the project, the test files will be copied to the unit testing bundle’s Resources directory.

Loading a File from the Unit Testing Bundle

To load an individual file call NSBundle’s pathForResource: method. The following code loads a test XML file from the unit testing bundle:

NSBundle *unitTestBundle = [NSBundle bundleForClass:[self class]];
NSString* xmlFilename = [unitTestBundle pathForResource:@"XMLTestFile" ofType:nil];
NSData* xmlData = [[NSData alloc] initWithContentsOfFile:xmlFilename];

Opening a File Wrapper

To open a file wrapper, call NSBundle’s URLForResource: method to find the file wrapper. Call NSFileWrapper’s initWithURL: method to open the file wrapper. The following code opens a file wrapper from the unit testing bundle:

NSBundle *unitTestBundle = [NSBundle bundleForClass:[self class]];
NSURL* testFileURL = [unitTestBundle URLForResource:@"FileWrapperTest" withExtension:@"wrap"];
NSFileWrapper* testFile = [[NSFileWrapper alloc] initWithURL:testFileURL options:0 error:nil];

Substitute your file wrapper’s file extension for “wrap” in the URLForResource: call. Read my Working with Cocoa File Packages post for more information on working with file wrappers.

Keep in mind that the initWithURL: method was added in Mac OS X 10.6. If you’re running an older version of Mac OS X, call NSFileWrapper’s initWithPath: method, which was deprecated in 10.6.

Comments (1) | Trackback

Working with Cocoa File Packages

Saving a Cocoa application’s data in a file package is not much more difficult than saving the data in a single file. This post shows you how to save your document’s data in a file package and read the data from the package.

Introduction to File Packages

A file package is a bundle, which is a collection of one or more directories that appears as a single file to the user. Xcode projects use file packages. Select an Xcode project in the Finder, right-click, and choose Show Package Contents to examine the file package.

When should you use a file package? Use a file package when you want to save your application’s document data in multiple files. A game level editor may want to save the level layout, the level’s enemy list, and the level’s treasure list in separate files. Screenwriting software may want to save each scene in its own text file.

The simplest file package is a single directory that contains all the files. But you can have multiple directories to group files if you’re going to store lots of files in your file package.

There are two tasks you must perform to add file package support to your application. First, you must add a document type for the package to your Xcode project’s target. Second, you must implement the methods fileWrapperOfType: and readFromFileWrapper: in your NSDocument subclass.

Adding a Document Type to Your Project

By adding a document type to your project’s target and setting the document type as a package, your document’s data will be saved as a file package. If you don’t add the document type, your data will appear as a folder in the Finder instead of as a single file, which makes it easier for someone to accidentally delete or move a file.

To access the inspector for your target, perform the following steps in Xcode:

  1. Select your target from the Groups and Files list.
  2. Click the Info button on the project window toolbar to open the target’s inspector.
  3. Click the Properties tab in the inspector.

At the bottom of the target inspector is a list of document types. You should see the following columns of information:

  • Name
  • UTI
  • Extensions
  • MIME Types
  • OS Types
  • Class
  • Icon File
  • Store Type
  • Role
  • Package

If you have a document-based Cocoa application project, there should be one document type in the list. Click the + button in the lower left corner of the inspector to add a document type if you need another one.

Mandatory Document Type Fields for File Packages

At a minimum, you must deal with the Name, Extensions, Role, and Package fields for your file package’s document type. For the Name field, give a description for your file type. For the Extensions field, list the file extension you want for your file type, skipping the period. If you wanted your file to have the extension .xyz, you would enter the following for the Extensions field:

xyz

Make sure the Role is Editor. An editor can read and write files. Select the Package checkbox to make your document type a file package.

The Other Document Type Fields

UTI stands for uniform type identifier, which identifies an abstract type, such as a file format. A UTI takes the following form:

com.CompanyName.DocumentType

MIME (Multimedia Email Extensions) types are file types a web browser uses. Web browsers don’t open file packages so you shouldn’t have to worry about setting MIME types.

OS Types are four-character codes for the document’s file type. Before Mac OS X, Mac applications used OS Types instead of file extensions to identify themselves as the creator of a particular file. Today, you should use file extensions and UTIs to identify a document type.

The Class field contains the name of your NSDocument subclass. The Icon File field contains the file name for the document icon. If you use a custom icon for your file package, enter the file name in the Icon File field. You should also add the icon file to your project.

The Store Type field is used by Core Data applications. If you’re not using Core Data, you can ignore this field.

Writing to a File Package

To write your document’s data to a file package, your NSDocument subclass must implement the method fileWrapperOfType:. This method takes the following form:

- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError **)outError

The file wrapper that fileWrapperOfType: returns is the main directory for the file package. The typeName argument identifies the type of document. You shouldn’t have to worry about the typeName argument. If there is a problem during the write, the outError argument will contain the reason for the problem.

In your implementation of fileWrapperOfType:, you must perform the following tasks:

  • Create a directory file wrapper.
  • Get your data into a NSData object.
  • Create a file wrapper and add the file to a directory file wrapper.

Creating a Directory File Wrapper

Call NSFileWrapper’s initDirectoryWithFileWrappers: method to create a directory file wrapper. The following code creates an empty directory:

NSFileWrapper* mainDirectory;
[mainDirectory initDirectoryWithFileWrappers:nil];

If you’re going to store all your files in one directory, you’re done with the directory file wrapper for now. But if you’re going to store your files in folders inside the file package, you must create a directory file wrapper for each folder and give that folder a preferred filename. Call NSFileWrapper’s setPreferredFilename: method to give the folder a preferred filename. The following code creates an empty directory named HTML inside the file package:

NSFileWrapper* htmlDirectory;
[htmlDirectory initDirectoryWithFileWrappers:nil];
[htmlDirectory setPreferredFilename:@"HTML"];

Getting Your Data into a NSData Object

NSFileWrapper’s methods use NSData objects to write data to files and read data from files. How you get your data into a NSData object depends on your application, but the following code shows how to convert the RTF data of a text view to NSData:

NSTextView* textView;
NSData* myData;
NSRange range = NSMakeRange(0, [[textView textStorage] length]);
myData = [[textView textStorage]RTFFromRange:range documentAttributes:nil];

The following code demonstrates how to convert an XML document to NSData:

NSXMLDocument* xmlDoc;
NSData* myData = [xmlDoc XMLData];

Creating a File Wrapper

You can create a file wrapper and add it to a directory file wrapper with one method call. Call NSFileWrapper’s addRegularFileWithContents: method. This method takes two arguments. The first argument is the NSData object you created, and the second argument is the filename you want for the file. The following code adds a file named “index” to the HTML directory:

NSFileWrapper* htmlDirectory;
NSData* myData;
[htmlDirectory addRegularFileWithContents:myData preferredFilename:@”index”];

Call addRegularFileWithContents: for each file you’re going to save in the file package.

To add a subdirectory to the directory, call NSFileWrapper’s addFileWrapper: method. The following code adds the HTML directory to the main directory:

NSFileWrapper* htmlDirectory;
NSFileWrapper* mainDirectory;
[mainDirectory addFileWrapper:htmlDirectory];

 

Reading from a File Package

To read from a file package, your NSDocument subclass must implement the method readFromFileWrapper:. This method takes the following form:

- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError **)outError

The fileWrapper argument is the main directory where all the files in the wrapper are stored. You can give it a more descriptive name than fileWrapper if you want. The typeName argument identifies the type of document. You shouldn’t have to worry about the typeName argument. If there is a problem during the read, the outError argument will contain the reason for the problem.

If you have all your files inside the main wrapper, call NSFileWrapper’s fileWrappers: method, which contains all the file wrappers inside a directory.

NSFileWrapper* mainDirectory;
NSDictionary* files = [mainDirectory fileWrappers];

If you need to find a particular file or directory, call NSFileWrapper’s objectForKey: method. The following code locates the HTML folder in the file wrapper:

NSFileWrapper* mainDirectory;
NSFileWrapper* htmlDirectory = [[mainDirectory fileWrappers] objectForKey:@”HTML"];

Once you get a directory, call fileWrappers: to get the list of files in that directory.

NSFileWrapper* htmlFiles = [htmlDirectory fileWrappers];

Reading the Individual Files

When you call NSFileWrapper’s fileWrappers: method, it returns a NSDictionary object containing all the files in the directory. Assuming you want to read all the files, the easiest thing to do is create an NSEnumerator object and use that object to go through the files.

A dictionary contains a list of key-value pairs. The enumerator for a dictionary can either be a key enumerator or an object enumerator. For a file wrapper you should use a key enumerator that contains all of the filenames in the dictionary.

NSDictionary* files;
NSEnumerator* fileEnumerator = [files keyEnumerator];

Use NSEnumerator’s nextObject: method to go through the files in the dictionary. Call NSEnumerator’s objectForKey: method to get the file wrapper. Call NSFileWrapper’s regularFileContents: method to get the contents of the file in a NSData object.

id currentFilename;
NSFileWrapper* currentFile;
NSData* myData;
while (currentFilename = [fileEnumerator nextObject]) {
	currentFile = [files objectForKey:currentFilename];
	myData = [currentFile regularFileContents];
	// Load myData.
}

Loading the Data

How you load the data depends on the data you’re storing. If you store RTF data, you can use NSTextStorage’s initWithRTF: method to load a text view with the contents of a text file.

NSTextView* textView;
NSData* myData;
[[textView textStorage] initWithRTF:myData documentAttributes:nil];

If you’re loading an XML document, call NSXMLDocument’s initWithData: method to load the document from disk to the NSXMLDocument.

NSError* error;
NSXMLDocument* xmlDoc;
[xmlDoc initWithData:myData options:NSXMLDocumentValidate error:&error];

 

Additional Information

Read Apple’s documentation for the NSFileWrapper, NSData, and NSDocument classes for more information on working with file packages.

Comments (3) | Trackback

Locating the Unit Testing Bundle in Cocoa

If you load test data from a file when unit testing a Cocoa application, you’ll need to access the unit testing bundle to find the file. Your first instinct might be to call NSBundle’s mainBundle: method. For a unit testing target, the main bundle should be the unit testing bundle, right? Unfortunately, the mainBundle: method is for application bundles, not unit testing bundles.

Call NSBundle’s bundleForClass: method to load the unit testing bundle. The following code demonstrates how to locate the unit testing bundle:

NSBundle *unitTestBundle = [NSBundle bundleForClass:[self class]];
No comments | Trackback

Using NSZombie with Instruments

Cocoa’s NSZombie class helps you discover where you are accessing a deallocated object in your code. For those of you running Snow Leopard, Instruments has support for NSZombie so you can use NSZombie without having to make any changes to your Xcode project.

Checking for zombies is pretty simple. Open your project in Xcode. Choose Run > Run With Performance Tool > Zombies. Your application will launch and Instruments will trace its execution. If your application accesses a deallocated object, a zombie message appears in Instruments.

Instruments Zombie Message.png

Clicking the focus button next to the memory address shows the history of that memory address. The Responsible Caller column tells you where you’re trying to access a deallocated object.

If the Run With Performance Tool option is grayed out in Xcode, launch Instruments (or choose File > New if Instruments is already running) and select the Zombies template. The Zombies template is available for Mac applications and iPhone applications running in the Simulator. Use the Target menu in the trace document window toolbar to choose your application. Click the Record button to start recording.

No comments | Trackback

Value Expressions: Where to Find Documentation

When you open a mapping model in a Core Data project, you’ll see that attributes and relationships have a value expression, and you have the option to change the value expression. For attributes the value expression is usually simple.

$source.AttributeName

I wanted to see what kinds of values you could give a value expression. I did a Google search and found some questions from people on mailing lists and message boards from people who wanted the same information as I did. But I found no answers. After doing some more searching, I learned an important piece of information.

A value expression is an object of the NSExpression class. With this information I could look at Apple’s documentation. The place to start is the NSExpression class reference. In the class reference is a link to Apple’s Predicate Programming Guide. After looking at both documents, I learned value expressions are a large topic with many possible values, too many for me to answer here. But I now know where to look when I need information on value expressions. Now you know where to look too.

No comments | Trackback

Getting Started with Mac Programming

A question that comes up often in Mac programming forums is “Where do I start?” I have gathered a bunch of information and put it in this post to answer this question. Hopefully this helps people new to Mac development and reduces the frequency of “Where do I start?” questions on the forums.

NOTE: The information in this post is for people writing Mac GUI applications, not games. You could write a Mac game without having to learn Objective-C and Cocoa. Getting started in game development would be a good topic for a future post.

Update for Lion and Xcode 4 Users (July 2011)

Mac OS X 10.7 does not include Xcode. Download Xcode from the Mac App Store. The version of Xcode on the Mac App Store includes the iPhone SDK. Xcode 4 integrates Interface Builder with Xcode. There is no separate Interface Builder application.

The Tools You Need

If you have a copy of Mac OS X, you have the tools you need to write Mac applications. Apple includes developer tools, the Xcode Tools, with every copy of Mac OS X. Insert your Mac OS X DVD. The Xcode installer is in the Optional Installs folder.

Apple constantly updates their developer tools. To get the latest version of Xcode, go to Apple’s developer site. You will have to sign up for a free ADC membership before you can download Xcode.

If you’re interested in iPhone development, download the iPhone SDK instead of Xcode. The iPhone SDK contains both the Xcode Tools and the iPhone SDK. If you download Xcode, then download the iPhone SDK at a later date, you’re downloading Xcode twice, and Xcode is a large download.

After installing Xcode you will have all the tools you need to write Mac applications. The tools you will use the most are Xcode and Interface Builder.

Xcode is an integrated development environment. You will use Xcode to write your source code and compile the source code into a Mac application. You will use Interface Builder to create the user interface for your applications.

Learning a Programming Language

Before you can write Mac applications, you must know a programming language. If you have never programmed before, you will have to learn one. What language should you learn?

If you’re serious about writing Mac applications, you should eventually learn Objective-C. The Cocoa framework uses Objective-C as well as the Cocoa Touch framework, which is used to write iPhone applications.

Your First Language

Should you learn Objective-C first? If you post this question on a Mac programming message board, you will generate a lot of heated responses. These responses generally fall into three camps.

Camp 1: Learn Objective-C first. You have to know Objective-C to write Cocoa applications so you should go ahead and learn Objective-C.

Camp 2: Learn C, then Objective-C. Objective-C is a superset of C. It takes C and adds support for object-oriented programming. By learning C, you’ll make progress towards learning Objective-C.

Camp 3: Learn a language like Python or Ruby first. C and Objective-C are too difficult for a beginner. Python and Ruby are easier to learn. Start with one of them. After you learn Python or Ruby, you can move on to Objective-C.

Which camp is right? It depends on you and how you plan on learning Objective-C. If you plan on learning Objective-C through online tutorials, Camp 2 is right. Most Objective-C tutorials assume you know C so you would have to learn C first to understand the tutorials.

If you plan on learning Objective-C through Stephen Kochan’s Objective-C book (Amazon link), Camp 1 is right. The book doesn’t assume you know C so you can use the book to learn Objective-C without learning C first.

If you start learning C or Objective-C and have a hard time understanding the material, Camp 3 is right. Start with another language and learn Objective-C later.

Resources for Learning Programming

The World Wide Web is full of programming tutorials. The following sites will give you a large collection of programming language tutorials:

Some of you will prefer learning programming from a book. You can find books on Mac programming, Python, and Ruby from the following publishers:

Three programming language books that would help someone interested in Mac programming are the following:

  • Learn C on the Mac by Dave Mark.
  • Learn Objective-C on the Mac by Mark Dalrymple and Scott Knaster. This book is a sequel of sorts to Learn C on the Mac and assumes you’ve read Learn C on the Mac.
  • Programming in Objective-C 2.0 by Stephen Kochan.

Learning Programming on a Mac

If you’re learning C or Objective-C, you can use Xcode to learn the language. Create a command-line tool project for each program you write. In Xcode 3.2 take the following steps to create a command-line tool project:

  1. Choose File > New Project in Xcode.
  2. Select Application under Mac OS X on the left side of the New Project Assistant.
  3. Select Command Line Tool at the top of the New Project Assistant.
  4. Pick your language from the Type pop-up menu. Choose Foundation for Objective-C.
  5. Click the Choose button.
  6. Name your project and pick a location to save it.
  7. Click the Save button.

Those of you running Xcode 4 should click the Next button after Step 3. The Choose button in Step 5 is named Next in Xcode 4. Step 6 is split in Xcode 4. You name the project when you pick a language.

For those of you running older versions of Xcode, create a Standard Tool project to learn C or a Foundation Tool project to learn Objective-C.

Some people think integrated development environments like Xcode hide too many details from beginning programmers, which hinders learning. They suggest beginners write their source code in a text editor and compile it from the Terminal application. For more information on compiling from the Terminal, read my Avoiding Xcode post.

If you’re learning Python or Ruby, I have good news. Mac OS X comes with Python and Ruby interpreters so you can start writing Python and Ruby programs right now. For Python and Ruby programming, I recommend using TextWrangler, which is a free text editor. You can write, run, and debug Python and Ruby programs inside TextWrangler. My TextWrangler and Interpreted Languages post has more information on using TextWrangler.

Learning Cocoa

After learning Objective-C, you can move on to learning Cocoa. Cocoa is Apple’s framework for writing GUI applications on Mac OS X. Cocoa is a large framework so prepare to spend a lot of time learning it.

Cocoa Books

When people ask for book recommendations on Mac programming message boards, the usual recommendation is Aaron Hillegass’ Cocoa Programming for Mac OS X (Amazon link). This book does a good job of teaching the fundamentals of Cocoa programming. Hillegass assumes the reader knows C so his book is not right for people completely new to programming.

Cocoa is becoming a hot topic for computer book publishers. You can find a lot of new and upcoming Cocoa books from the following publishers:

Cocoa Learning Resources

The biggest source for Cocoa documentation is Apple. You can read Apple’s documentation in Xcode by choosing Help > Developer Documentation. You can also read it online at Apple’s developer site.

Some other sites that will help you learn Cocoa are:

This list is obviously not exhaustive. Use your favorite search engine to find more resources for learning Cocoa development and learning programming in general.

No comments | Trackback

Xcode 3 Addition: Python and Ruby Cocoa Project Templates

Xcode 3 includes project templates for writing Cocoa applications in Python and Ruby. Those of you who were turned off of Cocoa by Objective C’s syntax can now give Cocoa development a try.

If you installed the developer examples when you installed Xcode 3, you will find on your hard drive many examples of Cocoa programs written in Python and Ruby. You can find the Python examples at the following location:

Developer/Examples/Python/PyObjC

You can find the Ruby examples at the following location:

Developer/Examples/Ruby/RubyCocoa
Comments (2) | Trackback

Interface Builder 3 Change: No Subclass Creation

Older versions of Interface Builder let you create subclasses for Cocoa applications and generate source code files for the subclasses you created. As an example, you could drag an OpenGL view to a window, subclass the OpenGL view, and have Interface Builder create .h and .m files for the subclass. But Interface Builder 3 no longer lets you create subclasses. How do you create the subclasses?

You create the subclasses the old-fashioned way: with source code. Create your subclasses in Xcode. Interface Builder and Xcode are synchronized so the subclasses you create in Xcode appear in Interface Builder as well.
 
Choose File > New File to create a new file. A window will open containing many file types for you to choose from. For a Cocoa program you would want one of the Cocoa files unless you’re writing a Cocoa program in Ruby, in which case you would use one of the Ruby files. Name your file and click the Finish button. You’ve created a subclass file.

Instantiating Your Class
After creating your class in Xcode, you may need to add an instance of your class to the nib file so you can make connections in Interface Builder. If Interface Builder’s library window is not open, open it by choosing Tools > Library. The library window contains Interface Builder’s user interface elements.
Select an NSObject from the library window. NSObject is a blue cube. Drag NSObject to the nib file window. An NSObject named Object should now appear in the nib file window. Choose Tools >Identity Inspector to open the identity inspector. Select the NSObject in the nib file window.
The top of identity inspector should have a combo box labeled Class. Choose your class from the list of classes in the combo box. The name of the NSObject instance in the nib file window will change from Object to the name of your class. Any outlets and actions in your class should appear in the identity inspector.
If your classes are not appearing in Interface Builder, choose File > Synchronize With Xcode in Interface Builder.
Comments (7) | Trackback
Powered by WordPress