Detective 1.1 is now available. This was a major rewrite to support Twitter’s 1.1 API and handle changes to Twitter’s authorization. Rather than asking you to log in to Twitter with a login window, it will now ask your permission to use your Mac’s Twitter accounts.

To celebrate the release, it’s now 50% off (0.99) through the end of WWDC on June 15.

For more information, visit the Detective page or download it here.

Detective is 50% off – only $0.99 – until Jan. 5!

Detective 1.0.3 is now available. It fixes some rendering bugs in Mountain Lion & a problem that sometimes caused it to stop updating when the Mac wakes from sleep. Version 1.0.3 runs only in Mountain Lion and uses notification center instead of Growl.

The next update will restore support for Growl & older versions of Mac OS X.

When I submitted an update to Detective, I discovered a few tricky things related to sandboxing and embedded helper apps.

In order to support ‘start at login’ in a sandboxed app, you need to embed a helper app that launches the main app (the entire process is described here). What I didn’t realize is that the helper app also has to be signed, or it will fail to let you start it at login. However, when you sign the helper app, it will include its own embedded provisioning profile, so when you try to submit your app, it will be rejected with the following message:

Invalid Provisioning Profile Location – The provisioning profile for your Mac OS X app must be located in the Contents directory of the main app bundle. A provisioning profile is optional, but you cannot submit more than one.

One of the suggestions in Apple’s developer forum is to remove the embedded profile from the helper app. Note that deleting the embedded profile doesn’t affect the actual code signing. After some experimentation, I found that the easiest way to do it is to add a Run Script build phase to the main application that deletes the profile from the helper app:

rm ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Library/LoginItems/

After doing this, I was able to submit the app successfully.

Apple will soon require all Mac apps submitted to the app store to be sandboxed for heightened security, which means it needs to request permission for doing even basic things like accessing files or connecting to the internet. A lot of things like disk burning aren’t allowed at all in the sandbox, and some things like sending AppleEvents require temporary exemptions which will be phased out.

One common task that’s complicated by the sandbox is adding a login item. A good tutorial on creating login items is available at delite studio, although some changes to the code are needed. That code follows Apple’s earlier guideline of using LSRegisterURL to register your helper app. However, I found that it always fails with error -10819. According to an Apple engineer in Apple’s developer forum, you should not call LSRegisterURL in a sandboxed app.

Note that your application can’t add itself as a login item. You need to write a simple helper app that launches your main app and it must be included in the main application bundle in the relative path /Contents/Library/LoginItems/.

The method for adding & removing a login item turned out to be very simple:

- (void)addLoginItem {
    NSString *ref = @"com.madebynotion.myLoginHelper";
	if (!SMLoginItemSetEnabled((CFStringRef)ref, true)) {
		NSLog(@"SMLoginItemSetEnabled failed.");

- (void)removeLoginItem {
    NSString *ref = @"com.madebynotion.myLoginHelper";
	if (!SMLoginItemSetEnabled((CFStringRef)ref, false)) {
		NSLog(@"SMLoginItemSetEnabled failed.");

If you need to find out whether your login item is enabled, here’s a way to do it:

    NSString *bundleID = @"com.madebynotion.myLoginHelper";
    NSArray * jobDicts = nil;
    jobDicts = (NSArray *)SMCopyAllJobDictionaries( kSMDomainUserLaunchd );
    // Note: Sandbox issue when using SMJobCopyDictionary()
    if ( (jobDicts != nil) && [jobDicts count] > 0 ) {
        BOOL bOnDemand = NO;
        for ( NSDictionary * job in jobDicts ) {
            if ( [bundleID isEqualToString:[job objectForKey:@"Label"]] ) {
                bOnDemand = [[job objectForKey:@"OnDemand"] boolValue];
        CFRelease((CFDictionaryRef)jobDicts); jobDicts = nil;
        return bOnDemand;
    return NO;

Although this method works and seems to be the preferred way to do it in the sandbox, it’s less optimal than the old non-sandbox method, since your login item won’t appear in the users & groups preference panel’s login items list and can only be turned on & off from your own application.

I haven’t had a chance to write any blog posts last week while I was at WWDC, but I had a great time and learned a lot. This may have been the most important WWDC in recent years.

While last year’s WWDC focused primarily on iOS, this year’s conference was about equally split between iOS 5 and Mac OS X Lion. Both systems share some major enhancements including iCloud storage and Objective C runtime improvements that make memory management easier and a lot faster. I can’t write about much of what I saw, since everything except the keynote is under NDA. I will say that I’m running iOS 5 on my iPad and Lion on my MacBook Air and I’m very happy with both and find them stable enough for regular use. I haven’t installed iOS 5 on my iPhone, though.

On Sunday I went on the annual bus Pilgrimage to Apple’s headquarters in Cupertino. As always, the only thing we were able to see was the Apple company store. I took advantage of it to pick up a USB Ethernet adapter for my MacBook Air, since large downloads aren’t allowed over wireless connections during WWDC. I was pleasantly surprised by how fast the MacBook Air USB adapter is and how it just works without any fuss, unlike USB ethernet adapters I’ve used in the past.

1 Infinite Loop

WWDC isn’t all work. There are also a few fun events, starting with Tuesday night’s Apple Design Awards and Stump The Experts. One highlight of the conference is always the Thursday night WWDC Bash. Since the Bash moved from Apple’s campus in previous years to Yerba Buena Garden across from Moscone Center, Apple has been getting major bands to perform at the Bash. In previous years they had Ozomotli, Barenaked Ladies, Cake, and OK Go. This year they got Michael Franti & Spearhead for a great show.

The conference ended at noon on Friday, so I took advantage of the rest of the day to enjoy San Francisco. I walked from Moscone to the Ferry Building & took lots of pictures, which you can see here. I only brought my Canon G12, since I didn’t feel like lugging the D90. I’m very happy with the results, both for still photos & videos.

When Apple introduced the MacBook Air late last year, they called it The Next Generation of MacBooks. I was really hoping the new MacBook Pro would inherit many of its attributes, such as the thin profile, light weight, and standard SSD. Although the new models are a nice improvement over the old MacBook Pro, with faster processors & improved graphics, they’re still just as big and heavy.

After using the 3 pound 13″ MacBook Air, I will never go back to a big laptop that weighs 5 pounds or more. I would have preferred to see much thinner MacBook Pros with no optical drive and a standard SSD drive. Despite the slower processor, I find my MacBook Air to be almost as fast as my old 15″ MacBook Pro thanks to the much faster SSD. I would still love to see a “super MacBook Air”, with the fastest CPU & graphics from the MacBook Pro and 8GB RAM, but without too much extra size & weight by keeping the SSD and eliminating the optical drive.