Advanced programming tips, tricks and hacks for Mac development in C/Objective-C and Cocoa.

StreamToMe iPhone App Released

StreamToMe is an iPhone app I've written that streams video in most common formats from your Mac to the iPhone/iPod Touch — without prior conversion or copying. It supports the TV out cable for the iPhone, turning your iPhone+Mac into a wireless media center. I'm excited that it's finally on the App Store because I've been using it non-stop during development and I'm really happy with it.

Use your Mac to stream it

Your Mac can store hundreds of gigabytes and play almost any video format (Xvid, Flash Video, WMV, MPG, MP4 and more), in almost any size (from QCIF to 1080p), but your iPhone cannot. Take advantage of this power; run the free ServeToMe app on your Mac and make it do all the hard work by live-transcoding video to your iPhone.

Then to watch video all you need to do is open the StreamToMe app on your iPhone or iPod Touch, select the video and 5 seconds later it's playing. No prior conversion, no archive of iPhone-encodes, no waiting, no copying, no syncing.

Download StreamToMe from the iTunes App Store and enjoy!

For more information, see the StreamToMe product page or the StreamToMe FAQ.

screenshot1.png screenshot5.png screenshot7.png
Read more...

Adding shadow effects to UITableView using CAGradientLayer

Shadows can be a useful effect, drawing attention to the content of your view by separating the view from the background. They also look cool. In this post, I'll show you how to add shadows to a UITableView using three CAGradientLayers — one above the first row, one after the last row and one for under the navigation bar.

ShadowedTableView app

In this post, I'll present the following sample app:

shadowedtableview.png

In this screenshot, the rows are dragged downwards to reveal the three different shadows: under the navigation bar, above the first row and under the last row.

Download the sample project ShadowedTableView.zip (26kB). Last updated 2009-08-23.

CAGradientLayer

The CAGradientLayer was a handy addition in iPhone SDK 3.0 — it allows you to create an efficient, linear gradient in just a couple lines. In this sample app, is it used to draw the opaque gradients on the table rows as well as the transparent gradients of the shadows.

While not as flexible as CGContextSetShadow (which can draw shadows around multiple shapes simultaneously or curved edges), CAGradientLayer is very fast, so it shouldn't have a noticeable effect on the speed of your application.

Since CAGradientLayer requires iPhone SDK 3.0, if you need a similar effect on earlier versions of iPhone OS, you'll need to use CGGradientCreateWithColorComponents to draw the contents of a CALayer yourself or perhaps use a UIImageView (an image view is another alternative if you need non-straight edges).

Required steps

Adding the shadows can be done using a UITableView subclass. In this subclass, we need to perform the following steps:

  • Create the three CAGradientLayers
  • Place them and make sure they stay underneath the table's rows
  • Update the positions of the gradients when the table scrolls or grows

We can perform all these steps by overriding the layoutSubviews method.

Constructing the gradients

The construction of the CAGradientLayers is done lazily (i.e. as needed) in layoutSubviews (removing the need to override any constructors.

The three gradients are all constructed using the following method. The inverse parameter is used to generate the smaller gradient that fades-out upwards (for the shadow above the first table row).

- (CAGradientLayer *)shadowAsInverse:(BOOL)inverse
{
    CAGradientLayer *newShadow = [[[CAGradientLayer alloc] init] autorelease];
    CGRect newShadowFrame =
        CGRectMake(0, 0, self.frame.size.width,
            inverse ? SHADOW_INVERSE_HEIGHT : SHADOW_HEIGHT);
    newShadow.frame = newShadowFrame;
    CGColorRef darkColor =
        [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:
            inverse ? (SHADOW_INVERSE_HEIGHT / SHADOW_HEIGHT) * 0.5 : 0.5].CGColor;
    CGColorRef lightColor =
        [self.backgroundColor colorWithAlphaComponent:0.0].CGColor;
    newShadow.colors =
        [NSArray arrayWithObjects:
            (id)(inverse ? lightColor : darkColor),
            (id)(inverse ? darkColor : lightColor),
        nil];
    return newShadow;
}

Notice that the gradient goes from 50% black to 0% of the table's background color. It is important to use the table's actual background color, even though it may seem as though the 0% opacity on it would make it invisible. The mathematics of the gradient will cause the middle color to be 12.5% black + 12.5% background color, so the background color does have an effect on the gradient.

Placing the gradients

The simplest gradient to place is the one under the navigation bar — we just put it at the top of the UITableView.

The only tricky part is that the view, as a child of the UITableView will scroll downwards when the view scrolls (we want it to stay still and not scroll). To fix this issue, we'll offset the view by the same amount as the scroll offset (to keep it in place) and perform this frame adjustment inside a CATransaction with animation disabled (so that these offsets are not visible).

[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];

//
// Stretch and place the origin shadow
//
CGRect originShadowFrame = originShadow.frame;
originShadowFrame.size.width = self.frame.size.width;
originShadowFrame.origin.y = self.contentOffset.y;
originShadow.frame = originShadowFrame;

[CATransaction commit];

The gradients on the rows are a little trickier. First, we only want to add them if the rows are visible. Second, we want to make them child layers of their respective rows so that if the rows animate, the shadows will follow them.

Update 2009-08-23: adding the shadows as children of the cells themselves is a new addition to improve performance during animation from the original post (which arranged the rows directly in the UITableView.
NSIndexPath *firstRow = [indexPathsForVisibleRows objectAtIndex:0];
if ([firstRow section] == 0 && [firstRow row] == 0)
{
    UIView *cell = [self cellForRowAtIndexPath:firstRow];
    if (!topShadow)
    {
        topShadow = [[self shadowAsInverse:YES] retain];
        [cell.layer insertSublayer:topShadow atIndex:0];
    }
    else if ([cell.layer.sublayers indexOfObjectIdenticalTo:topShadow] != 0)
    {
        [cell.layer insertSublayer:topShadow atIndex:0];
    }

    CGRect shadowFrame = topShadow.frame;
    shadowFrame.size.width = cell.frame.size.width;
    shadowFrame.origin.y = -SHADOW_INVERSE_HEIGHT;
    topShadow.frame = shadowFrame;
}
else
{
    // Remove the shadow if it isn't visible
    [topShadow removeFromSuperlayer];
    [topShadow release];
    topShadow = nil;
}

This is the placement of the top shadow (the one above the first row). We only attend to it if the first row is visible and when we do, we always ensure that it is the 0-th sublayer of the appropriate cell.

Other tidbits

The sample project also contains the GradientView class which is a very simple subclass of UIView that sets the UIView's layerClass to CAGradientLayer and uses that to draw a gradient across the view. This is the view that is set as the UITableViewCell's backgroundView to draw the views in this sample project.

The project also contains the ClearLabelsCellView which is a UITableViewCell subclass that overrides setSelected:animate: to fix the fact that this method always sets the default textLabel and detailTextLabel backgroundColor to white — instead setting it to clearColor so you can combine the default text labels with non-white cell backgrounds.

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];

    self.textLabel.backgroundColor = [UIColor clearColor];
    self.detailTextLabel.backgroundColor = [UIColor clearColor];
}

Conclusion

Download the sample project ShadowedTableView.zip (26kB). Last updated 2009-08-23.

The ShadowedTableView is self-contained so you can drop it easily into a project.

In its current form, it only works well for full-width, rectangular, contiguous tables — so it works well for "plain" tables but is not suited to "grouped" tables. It also looks best if the table row separator is set to "none".

Read more...

Animating a window to fullscreen on the Mac

Many Mac OS X applications animate their regular application windows to fullscreen but since there's no dedicated method for the task, there's no standard Apple documentation that covers the operation. If you look for examples on the web, you'll find numerous examples that perform this operation using old APIs or methods intended for permanently fullscreen games (the wrong approach for an application window). In this post I'll show you my preferred approach for making an application window fullscreen, with continuous display and smooth animation.

Introduction

The sample app for this post has one window:

fullscreenwindow.png

When you choose the "Toggle Fullscreen" menu item, the content area of the window (not the window's frame) will animate to fill the entire screen (the Dock and menubar will hide).

You can download the Xcode 3.1 project here: FullscreenImage.zip (277kB).

Fullscreen windows

There are two basic types of fullscreen on the Mac:

  1. Screen capture — where one window gains exclusive control of the screen.
  2. Basic fullscreen window — where a floating window simply covers everything else on the screen.

The first is normally recommended for games where you never want the window to be interrupted. For an example of this type of window, see my earlier post An Asteroids-Style Game in Core Animation: Part 1.

The second option has the advantage that it can be interrupted by other windows. For example: Exposé, Command-Tab and notification windows will continue to function. This is the approach that I will detail in this post.

Animating a window to fullscreen

Zooming a window to the full size of the screen can be done using the setFrame:display:animate: method:

[mainWindow
    setFrame:[mainWindow frameRectForContentRect:[[mainWindow screen] frame]]
    display:YES
    animate:YES];

This will smoothly zoom a window to fill the available screen area.

On its own though, it has a few limitations:

  • It doesn't hide the menubar or Dock — so it doesn't actually fill the screen.
  • It doesn't hide the frame of the window, so the frame remains visible around the outside.
  • It doesn't change the window's level so panels and other window may still overlap the expanded window.

Hiding the menubar and Dock

There are two different ways to hide the menubar and the Dock. They are:

SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);

and

[NSMenu setMenuBarVisible:NO];

Despite the wording of the NSMenu method, it does also hide the Dock.

The first method has more options — the biggest of which is the kUIOptionAutoShowMenuBar which will reshow the menubar if you move the mouse to the top of the screen — but unfortunately, this method is Carbon, not Cocoa, so it requires you to link against the Carbon frameworks.

I prefer the NSMenu method for its simplicity and Cocoa linkage. This does mean that if you want to show the menubar, you'd need to create an NSTrackingArea or something similar to detect when the mouse is over the menubar and show it again.

Update 2009-09-20: In Snow Leopard, you can use the -[NSApplication setPresentationOptions:] method to handle all that SetSystemUIMode could do and more, all with standard Cocoa linkage.

The other consideration with hiding the menubar is that you should only do it if the window you are resizing is actually on the [[NSScreen screens] objectAtIndex:0] (the screen that shows the menubar). i.e.:

if ([[mainWindow screen] isEqual:[[NSScreen screens] objectAtIndex:0]])
{
    [NSMenu setMenuBarVisible:NO];
}

Hiding the window frame

The next issue to address is the window frame: we need to hide it. The problem is that you can't hide the frame of an existing window.

Instead, what we do is create a new, borderless window and swap the contentView from the old window into the new window.

fullscreenWindow = [[FullscreenWindow alloc]
    initWithContentRect:[mainWindow contentRectForFrameRect:[mainWindow frame]]
    styleMask:NSBorderlessWindowMask
    backing:NSBackingStoreBuffered
    defer:YES
    screen:[mainWindow screen]];
[fullscreenWindow setLevel:NSFloatingWindowLevel];
[fullscreenWindow setContentView:[mainWindow contentView]];
[fullscreenWindow setTitle:[mainWindow title]];
[fullscreenWindow makeKeyAndOrderFront:nil];

There are three other points to notice in this code:

  • The window level is NSFloatingWindowLevel (so that the window will appear above NSPanels and similar windows).
  • The window title is set to match the old window (so that the new window will appear the same in Exposé).
  • The window is actually a subclass so we can override the method canBecomeKeyWindow to return YES (since it returns NO by default for NSBorderlessWindowMask windows).

Other minor points

When swapping the contentView between windows, the destination window should not be onscreen (orderedOut:) otherwise flicker can occur.

If you run the whole process while the window is miniaturized in the Dock, numerous screen glitches will occur. It's best to deminiaturize before you start (this must happen before you hide the Dock).

Conclusion

You can download the Xcode 3.1 project here: FullscreenImage.zip (277kB).

The whole code is one method in the sample app — an easy copy and paste into any application.

Read more...

Safe, threaded design and inter-thread communication

The Foundation framework provides all the tools you need for inter-thread communication — without needing to handling locks and synchronization yourself. I'll show you Cocoa's tools for inter-thread communication, notifications and easy synchronization — including far simpler code for posting NSNotifications on the main thread than the Cocoa documentation suggests.

Introduction

Multi-threaded code has a reputation for being difficult to write and prone to deadlocks, race conditions and unpredictable behaviors.

While this remains true if you are forced to handle locking yourself, Foundation provides all the tools you require to manage typical multi-threaded situations so that you can avoid the risks of locks entirely.

This post comes from my shock at Apple's suggestion (in their Delivering Notifications To Particular Threads) that something as simple as sending an NSNotification from one thread to another should take dozens of lines of code and a dedicated class for the purpose.

It isn't that difficult. Sending data between threads (including notifications) is a one line job.

Rules for safe simple threading

Simple thread safety in Cocoa requires just two rules:

  1. Every variable or object must nominally belong to a thread (although may be completely handed over to a different thread) and must not be used in multiple threads without handover (unless it is on the list of explicity thread-safe classes).
  2. All communication between threads (after thread startup) should use performSelector:onThread:withObject:waitUntilDone: and both the receiver and the "object" should belong (or be handed over) to the target thread.

The only limitation with this approach is that any thread that receives communication must be running an NSRunLoop. Since communication after construction is normally one-way (from worker thread back to the main thread) this is rarely a significant limitation. Other threads can invoke the [NSRunLoop currentRunLoop]'s runMode:beforeDate to process the run loop and receive messages.

A lot of multi-threaded code does not follow these rules. A lot of multi-threaded code uses a careful system of locks, synchronized sections, volatile variables and atomic operations to allow single objects to be accessed simultaneously from multiple threads. This does work but generally speaking: you don't want to design code this way. It's tricky, confusing and prone to errors. To illustrate, you can have a look at how many changes I had to make to my AudioStreamer code between the first and second versions — rock-solid, manually-locked code is annoying and difficult to write.

Instead, as much as possible, you should write your code to keep all objects compartmentalized to individual threads and to restrict communication between threads to method invocations using performSelector:onThread:withObject:waitUntilDone:. To explain how this works, I'll show a simple example of something running in a separate thread and show how it can perform all its inter-thread communication using existing Foundation methods to automatically handle all thread safety issues.

Scenario: writing to a network NSFileHandle in a worker thread

One of the simplest situations where you may want multithreading is writing to a network socket's NSFileHandle. This is a synchronous operation (blocks until complete) so it is a good idea to perform this in a worker thread so that the main thread of your program can remain responsive.

The following class is an NSOperation subclass. If you don't know about NSOperation, it's an object that you can add to an NSOperationQueue to have the object's main method run in a separate thread (see Apple's Threading Programming Guide). While NSThread's detachNewThreadSelector:toTarget:withObject: is the best way to launch a single worker thread, NSOperations are the best way to run a series threaded tasks (or has been since serious bugs in it were fixed in 10.5.7).

This class' init method takes an NSFileHandle and an NSData object on construction and writes the data to the file handle in its main method.

@interface FileWriteOperation : NSOperation
{
    NSFileHandle *fileHandle;
    NSData *data;
}
@end

@implementation FileWriteOperation

- (id)initWithFileHandle:(NSFileHandle *)aFileHandle data:(NSData *)aData
{
    self = [super init];
    if (self != nil)
    {
        fileHandle = [aFileHandle retain];
        data = [aData retain];
    }
    return self;
}

- (void)main
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    @try
    {
        [fileHandle writeData:data];
        // At this point, the write has succeeded
    }
    @catch (NSException *e)
    {
        // At this point, the write has failed
    }
    @finally
    {
        [pool drain];
    }
}

- (void)dealloc
{
    [fileHandle closeFile];
    [fileHandle release];
    [data release];
    [super dealloc];
}

@end

The NSOperation is created and its thread is launched as follows:

// Assume the operationQueue, fileHandle and fileData already exist
[operationQueue
    addOperation:
        [[[FileWriteOperation alloc]
            initWithFileHandle:fileHandle
            data:fileData]
        autorelease]];

To follow the first rule of thread-safety, after the FileWriteOperation is constructed, the fileHandle value passed into its initWithFileHandle:data: method cannot be used again outside the NSOperationQueue's worker thread.

Communicating success or failure

The above class works but doesn't have any way of communicating the result. What would be good is if we could send a writeFinishedWithSuccess:YES at the "write has succeeded" line and a writeFinishedWithSuccess:NO at the "write has failed" line.

The unsafe way of doing this is to invoke a method on another object sending the result:

    [responseHandler writeFinishedWithSuccess:YES]; // BAD!!

If responseHandle belongs to another thread, then this method could cause any number of race conditions and other multi-threading issues.

The solution though is exceptionally simple:

    [responseHandler
        performSelectorOnMainThread:@selector(writeFinishedWithSuccess:)
        withObject:[NSNumber numberWithBool:YES]
        waitUntilDone:NO];

This assumes that responseHandler is a nominally "main thread" object. You can use the performSelector:onThread:withObject:waitUntilDone: method and specify a different thread if you need to send the response elsewhere.

You'll notice that the parameter has to be an object in this case ([NSNumber numberWithBool:YES] instead of simply YES) and any parameter that you pass to another thread should not be used again in the current thread.

Delivering Notifications To Particular Threads

Of course, the FileWriteOperation class shown above doesn't have a responseHandle object that it can notify when it is done, so I'd rather use an NSNotification sent to the NSNotificationCenter and if any object wants to receive the notification, it can.

The NSNotificationCenter itself is thread-safe but it delivers the notifications on the thread in which you invoke postNotification: so if you expect the observers of that notification to belong to a different thread, you've just broken those objects' thread safety.

Normally it is a good idea to deliver all notifications on the main thread. We can do this as follows:

// Line of code in some method of some class...
[[self class]
    performSelectorOnMainThread:@selector(postNotification:)
    withObject:
        [NSNotification
            notificationWithName:@"FileWriteOperationSucceeded"
            object:self]];

+ (void)postNotification:(NSNotification *)aNotification
{
    [[NSNotificationCenter defaultCenter] postNofication:aNotification];
}

Notice that we don't call +[NSNotificationCenter defaultCenter] until we're on the main thread. If we call it on the other thread, it will return the notification center for that other thread.

Some classes follow a different behavior of delivering notifications to the thread on which they were constructed (not necessarily the main thread). To follow this behavior, simply save the [NSThread currentThread] on construction and perform the postNotification: selector on that thread.

You may have noticed that the notification is passing "self" from one thread to another — breaking the thread ownership of "self". This is only really safe in one of the following cases:

  • Handover — the parameter will never be used on this thread again.
    In this case, if "self" is complete on the worker thread (for example at the bottom of the main method shown above). In this case, the object is passing itself back to the other thread.
  • Thread-safe — if the class or specific methods are guaranteed to be thread-safe.
    In this case, if the only methods invoked on self are retain, release and the default pointer comparison isEqual: method (these are the methods invoked when removing from an NSMutableArray). These are guaranteed thread-safe methods.

If neither of these are true then you can't pass self (or any other parameter) safely between threads.

Conclusion

The purpose of this article is to communicate two ideas:

  • Carefully locking and synchronizing makes programming hard but if you design your objects to be used on only one thread at a time, then they are thread-safe without the need for locks or synchronzation (if needed, split your objects into components for separate threads).
  • Use inter-thread communication to keep objects in separate threads up-to-date with each other. In Cocoa, this is very simple but you do need to ensure that all parameters you pass are either thread-safe objects or handover objects.

Apple's Delivering Notifications To Particular Threads code is woefully out of date. I would be happy to see it completely changed — you should never need to implement something so complex just to deliver notifications to another thread.

Of couse, there are always situations where you can't run an NSRunLoop to process performSelector:onThread:withObject:waitUntilDone: messages. There are also situations where you feel that one of your object s must be multi-threaded (rather than single thread exclusive). Either of these cases requires will require a synchronized or locked solution but my recommendation is to try to find an NSRunLoop and thread exclusive solution first as these are significantly easier to manage and prone to fewer potential problems.

Read more...

Control and configuration of applications through Info.plist

The Info.plist file is home to the metadata about your application used by the operating system. Most Cocoa programmers know that it stores the bundle identifier, icon name and version number of an application but the Info.plist can also control access to essential iPhone hardware resources and can change the very nature of your Mac OS X applications. In this post, I'll cover basic Info.plist usage and also explain some of the rarer settings.

The default fields: identifiers, icons and version numbers

The Info.plist is the main source of metadata that the operating system has to learn information about your application. Everything that the operating system may need to know (names, icons, supported file types, URL types, NSApplication subclasses) may all be specified in the Info.plist.

The file itself is familiar to almost all Cocoa programmers since Xcode inserts one automatically into every project created from an application template.

Probably the most common settings are:

  • CFBundleIdentifier — the identifier for your application, normally in the form "com.[company].[application]". The iPhone tries to fill in the application name from the Project's product name but will fail if you ever try to build for a device if there are spaces or differences in capitalization so you may just want to set this manually.
  • CFBundleIconFile — almost every application I ship names its icon "Icon.[png/icns]". Why Apple can't put a default value here, I don't know.
  • CFBundleVersion — this isn't the only version number since you can embed a different version in the Project Settings. If you don't have a formal build system in place though, you'll need to make certain that this number is always different every time you give a build to someone to test (a crash log is useless if you can't guarantee which version it came from).

How Xcode builds the Info.plist

Most of the other settings that are in the Info.plist by default have either good initial values (like the NSMainNibFile value which is automatically set to the name of the Nib file that the template itself includes), are largely pointless (like the CFBundleSignature which is a campy Mac OS 9 throwback) or are generated from values defined in the Project settings (like the CFBundleName).

The reason why the fields in the Info.plist can be generated from "Project settings" fields is that their values are filled in when the Info.plist file is "built" by Xcode.

Building the Info.plist file is actually the first stage in building for an application — the file is build immediately after creating the directory structure for the bundle. You can control the Info.plist's build settings in the "Packaging" section of the Target settings — including setting a different Info.plist for each target if needed.

While most stages of a build are handled by an external process (like gcc or ibtool), if you look through the Build Results window log, you'll notice that the Info.plist is built by a tool that identifies itself as <com.apple.tools.info-plist-utility>. This isn't actually a separate tool — it is described as "abstract" by the "Built-in compilers.pbcompspec" in the DevToolCore.framework and is implemented internally by the XCInfoPlistUtilityCommandInvocation class in the DevToolCore.framework. Xcode passes the arguments into this class (like it would for any other build stage) and the class parses the arguments and handles all the work itself.

Reading from the Info.plist in your program

While the Info.plist is primary metadata that your program advertises to the operating system and external programs, your program can easily read any of the fields in the Info.plist for its own purposes using the NSBundle's infoDictionary. For example, the URL to an iPhone app's own page on the iTunes App Store would be:

NSURL *appURL =
    [NSURL URLWithString:[NSString stringWithFormat:@"http://www.itunes.com/app/%@",
        [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]
            stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];

Technically, you can store anything you want in the Info.plist file but generally your own data would be better put in a separate file so you don't slow the operating system down when it needs to open and parse the Info.plist file to extract the data it needs.

Essential iPhone Info.plist entries

Aesthetic settings

A number of fields are required in the Info.plist to control aesthetic options for an iPhone application. These include:

  • UIPrerenderedIcon — set this to disable the gloss highlight that the Springboard applies to your application's icon.
  • UIInterfaceOrientation — use this to start your application in a non-portrait orientation.
  • UIStatusBarStyle — use this to enable black or transparent status bars.
Avoiding WiFi disconnections

Any iPhone application that requires a WiFi connection must set UIRequiresPersistentWiFi to <true/>, otherwise the iPhone will abruptly disconnect the WiFi connection after 30 minutes of use. No warning, no error: 30 minutes and you'll lose WiFi without this flag.

Invoking your iPhone application by URL

The CFBundleURLTypes key allows you to specify URL schemes that will cause the iPhone to switch to your application. No, you can't override the schemes for the built-in applications.

If your application is launched using a URL type named scheme, then you can also provide a different startup image "Default-scheme.png" instead of the regular "Default.png".

Essential Mac OS X Info.plist entries

Changing the application type

You can create an application with no UI — no appearance in the Dock, no menubar and can't be the frontmost application — by setting the LSBackgroundOnly flag in the Info.plist.

Realistically though, you're more likely to make your application LSUIElement. The reason why this flag is better, is that your application can be frontmost if you open a window but the other traits of LSBackgroundOnly remain (no appearance in the Dock and no menubar).

Why would you want an application with no appearance in the Dock? If you're making an NSStatusBar application (little applications that run as icons in the right of the menubar).

The following code adds an entry to the status bar when the application starts:

- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
    statusItem = [[[NSStatusBar systemStatusBar]
        statusItemWithLength:NSSquareStatusItemLength] retain];
    
    [statusItem setMenu:statusMenu];
    [statusItem setHighlightMode:YES];
    [statusItem setToolTip:@"My Status Bar App"];
    [statusItem setTitle:nil];
    [statusItem setImage:[NSImage imageNamed:@"StatusBarIcon"]];
}
Accepted document types

Document types accepted by an application can be set by editing the Info.plist file (the CFBundleDocumentTypes dictionary) although this is easier to do on the Properties tab of the Target settings.

Conclusion

Looking back over the range of topics that I've covered, you can see that the Info.plist controls a very diverse range of application settings and capabilities. There are many more options that I haven't listed here so if you're trying to specify default localizations, change the NSApplication subclass, set environment variables for your application, specify a minimum OS versions and more, have a look at Runtime Configuration Guidelines: Property List Key Reference for other fields.

Read more...