Mac Development

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

SDL OpenGL Typedef Redefinition Error on Mac OS X 10.7

If you build a SDL OpenGL application for Mac OS X using SDL 1.2.14 (using the binary installer at the SDL site) and the Mac OS X 10.7 SDK, you can get the following error:

typedef redefinition with different types ('unsigned int' vs 'void *')

The SDL header file SDL_opengl.h and the OpenGL header file glext.h both define a data type GLhandleARB. SDL_opengl.h defines it as an unsigned integer. The Mac OS X 10.7 version of glext.h defines it as a void pointer. Defining GLhandleARB as different data types in different header files causes problems.

The SDL team fixed the problem in their Mercurial repository. The fix is not too difficult to apply. In your SDL_opengl.h file, change the following line of code:

typedef unsigned int GLhandleARB;

To the following:

#if defined(__APPLE__)    
	typedef void *GLhandleARB;
#else    
	typedef unsigned int GLhandleARB;
#endif

The fix defines GLhandleARB as a void pointer on Mac OS X and an unsigned integer on other operating systems.

No comments | Trackback

Installing Mac Compilers Without Using Xcode

Most programmers with a Mac install Xcode when they need a C/C++/Objective-C compiler because Xcode includes everything you need to write C, C++, and Objective C code on Mac OS X. Xcode is a large download and is excessive if all you want to do is install GCC, LLVM, or Clang on your Mac. Kenneth Reitz has placed a compiler installer on Github that lets you install GCC, LLVM, and Clang without downloading Xcode. You can learn more by clicking the following link:

os-x-gcc-installer

I have Xcode installed so I haven’t tested the installer, but I know some Mac-using programmers will be interested in this.

No comments | Trackback

Xcode 4: Unit Testing 64-Bit Mac Apps

When unit-testing a 64-bit Mac application, make sure the class files you are testing (your application’s implementation files) are members of the unit testing target. You will get linker errors when building the unit testing target if the class files being tested are not members of the unit testing target.

How do you make a file a member of the unit testing target? Open the file inspector by choosing View > Utilities > File Inspector. Select the file from the project navigator. Select the checkbox for the unit testing target in the Target Membership section of the file inspector.

Xcode4TargetMembership

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

Xcode 4: Adding a Framework to Your Project

To add a framework to your project, select the project file from the project navigator on the left side of the project window. Select the target from the project settings editor. Click the Summary button at the top of the editor.

Xcode 4 Linked Frameworks

You will see a list of linked frameworks and libraries in the project settings editor. Click the + button to add a framework or library to your project.

Comments (10) | Trackback

Apple’s Mac Developer Program

With the advent of the Mac App Store, I’ve noticed some confusion from new Mac developers on some message boards. They think they must sign up for Apple’s Mac Developer Program and pay $99 a year to develop applications for Mac OS X.

You don’t have to sign up for the paid developer program to download Xcode or to write Mac applications. A free developer account with Apple is all you need. If you don’t care about having the latest version of Xcode, you can install Xcode from your Mac OS X install disc and avoid signing up for any developer account with Apple.

There are two reasons to sign up for the Mac Developer Program and pay the $99 annual fee.

Reason 1: You need access to pre-release versions of Mac OS X

As I’m writing this post, the current version of Mac OS X is 10.6.6. Members of the paid developer program get access to pre-release versions of 10.6.7 and 10.7 so they can test their applications on upcoming versions of Mac OS X. Generally developers with shipping applications are the ones with the greatest need for access to pre-release versions of Mac OS X. They must make sure the new version of Mac OS X doesn’t break their application. If you’re new to Mac development, you most likely don’t need this access.

Reason 2: You want your application in the Mac App Store

If you want to be in the Mac App Store, you must pay the $99. But you can wait until you’re close to releasing your application to sign up for the paid developer program.

Those of you who have no need for pre-release versions of Mac OS X and the Mac App Store can save your money while you develop Mac applications.

Comments (4) | Trackback

Mac Game Programming Roadmap Update for 2010

I’ve seen several questions on Mac programming forums recently looking for Mac game programming tutorials, which made me look at my Mac Game Programming Roadmap article. The article is three years old, and some things have changed in the past three years.

Avoid Carbon

Unless you have an existing Carbon codebase, avoid Carbon. If you don’t want to learn Objective-C, use a cross-platform game framework like SDLAllegro, or SFML. Another advantage of using one of these frameworks is you can support Windows and Linux without having to rewrite much of your code.

SFML

SFML is a cross-platform game framework that has grown in popularity in the past three years. I have not used SFML, but go the SFML website and see if it meets your needs.

Unity

Unity existed in 2007, but its Indie version cost $199. The Indie version is now free and lets you write games that run on Mac OS X, Windows, and the Web. If you’re interested in creating 3D games, I recommend Unity. You’ll finish your game much faster with Unity than you will by programming your game from scratch.

No comments | 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
Powered by WordPress