I recently announced Nori for Mac as part of weekly app challenge. Nori is a simple Mac app to instantly create photo strips by simply dragging and dropping images to Nori's window. Nori is not my first Mac app; I had released Kaomoji for Mac and Tiny for Mac before. However, both Kaomoji and Tiny are not window-based Mac app. On the other hand, Nori is a window-based application which gave me the opportunity to learn the complexity of making a Mac app with user interface.

Nori for Mac icon

In this post, I will share some interesting stuff I encountered when developing Nori.

AppDelegate in Mac is clueless about the UI

When creating a new iOS project in Xcode, we will get a reference to window in AppDelegate class, while a Mac app does not have window property. It feels like the AppDelegate in Mac does not know even what the initial window is. This is probably due to the fact that a Mac app can have multiple windows.

// AppDelegate in iOS
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;

// AppDelegate in OSX
#import "AppDelegate.h"
@interface AppDelegate ()
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application

In Nori's case, I need to the app to generate photo strips when the app receive user-selected images from Finder. When the user click Create Photostrip from Finder's contextual menu (right click), the system will call a registered method in Nori. Inside this method, the app will fetch the selected file URLs and generate photo strip from the image files.

- (void)photoStripFromImage:(NSPasteboard *)pboard userData:(NSString *)data error:(NSString **)error {
    NSArray *classes = @[ [NSURL class] ];
    NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: [NSNumber numberWithBool:YES],
                               NSPasteboardURLReadingContentsConformToTypesKey: [NSImage imageTypes] };
    NSArray *fileURLs = [pboard readObjectsForClasses:classes options:options];

    if (fileURLs) {
        NSArray* filenames = [pboard propertyListForType: NSFilenamesPboardType];

        // generate photo strip from files

I need to pass this file URLs to the view controller who is responsible in displaying the generated photo strip. In iOS, I can simply get the root view controller like so.

UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];

But the problem was there's no straight forward way to tell the view controller about these files. There are several ways to solve this, but I chose to disable the "magic" of storyboard by unchecking the "Is Initial Controller" in the main storyboard. Then I create the window programmatically in AppDelegate's applicationDidFinishLaunching.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {    
    NSStoryboard *mainStoryBoard = [NSStoryboard storyboardWithName:@"Main" bundle:nil];
    MainWindowController *windowController = [mainStoryBoard instantiateControllerWithIdentifier:@"mainWindowController"];
    self.mainWindowController = windowController;
    [self.mainWindowController.window makeKeyAndOrderFront:self];
    [windowController showWindow:self];

This way, the AppDelegate can refer to the main window controller and "speak" to it when the app receives images.

NSImage size is not pixel size

NSImage has a size property, but unfortunately it is not the pixel size of the image. The size property returns the size information that is screen resolution dependent. To get the pixel size, we need to use NSImageRep.

// NSImage category
- (NSSize)pixelSize {
    NSImageRep *rep = [[self representations] objectAtIndex:0];
    NSSize imageSize = NSMakeSize(rep.pixelsWide, rep.pixelsHigh);
    return imageSize;

NSView does not have background color

In iOS, changing the background color of a UIView is as simple as changing its backgroundColor property. But it's not that simple with NSView. I need to subclass NSView and override drawRect method.

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    [[NSColor colorWithDeviceWhite:0 alpha:0.7] setFill];
    [NSBezierPath fillRect:dirtyRect];

App Sandbox is annoying

While I understand the security reason for App Sandbox, I don't understand why opening Open Panel and Save Panel to open and save files, respectively, requires read/write permission. Selecting and saving files via Open panel and save panel are user initiated process. It's not something the app does in the background without user's knowledge. What's worse than its iOS counterpart is that in Mac, the system does not show an alert to ask user for permission.

Nori for Mac is now available on the Mac App Store. Check it out! :)



Blog about iOS, Programming, Japan, or Random Stuff.

« Older Newer »