Making a Mac App
This post will be a lot longer than normal. Its a very high level collection of experiences I had when writing my first Mac App. The app is called Cartwheel. Its a GUI on top of Carthage. Its far from done, but its totally open source and I will happily accept critique and pull requests :)
Mac OS X and iOS share a lot of their internals. Foundation, Core Image, AV Foundation, Grand Central Dispatch, NSURLSession are all shared and their API’s are almost the same between the two platforms. The notable exception is UIKit vs AppKit. These are the UI frameworks and they’re both pretty different. This is partly necessitated by the fact that AppKit is built for mouse input and UIKit is built for multitouch. That being said, there is also the elephant in the room: time. AppKit has been evolving since 1989 on NeXT systems whereas UIKit was a reboot started somewhere in the mid 2000’s. While UIKit shares the general style of AppKit, AppKit has a lot more power (which means complexity) and a lot more cruft leftover from the old days (which also means complexity).
NSWindow / NSWindowController / NSViewController
While iOS has a UIWindow, its something you don’t really interact with. A single UIWindow is created for you, then you fill the it with a UINavigationController and then you stack UIViewControllers within the UINavigationController. On OS X, things are a bit more complicated. You can have multiple windows. Each window should be managed by a single NSWindowController.
The NSWindowController helps configure the window’s appearance and handles the window lifecycle. On top of that, OS X has an NSViewController, but its a little different than iOS. Each NSWindowController can and usually does contain multiple NSViewControllers. For instance, the NSWindowController has a special slot for an NSTitlebarAccessoryViewController that holds a custom Toolbar. On top of that, its best to create an NSViewController subclass to control the main view in the window. So generally, each NSWindowController contains two NSViewControllers. That means instead of the iOS standard of 1 UIViewController to 1 “Screen,” OS X has 1 NSWindowController and 2 NSViewControllers to 1 “Screen.”
This may not seem like a big deal, but on OS X you really need to have a better understanding of how to safely communicate from these different objects in order to get an app that runs without crashing and without memory leaks.
What are Files?
LOL. The basis for the computer programs we all know and love since the beginning of personal computing, files and the filesystem, is something you basically don’t have to deal with in iOS. Well, in OS X, you have it and you should love it and embrace it. But don’t abuse it. Its perfectly simple to create a NSURL with a file://~/Documents/Whatever path. But don’t do it! You never know if Apple will move one of those folders and it won’t work if you sandbox your app. The Mac App Store requires your app to be sandboxed, so its no joke. Instead, use NSSearchPathForDirectoriesInDomains to get a path to the Application Support folder or the Preferences folder or the Documents folder. When you want to append onto that URL, use NSURL method URLByAppendingPathComponent. On top of that, if you want to open a file that is outside your sandbox, remember to use NSOpenPanel and/or NSSavePanel to let the user select it.
The big question is how to read and write data from and to the disk. OS X has NSFileManager and NSFileCoordinator. If you’re going to be reading and writing files that other apps, cloud services, etc may also be reading/writing to simultaneously use NSFileCoordinator (and good luck). If you’re going to be doing lots of reads and writes, use NSFileManager. I have used this in my DataSource class. Its pretty easy, but its not the easiest way.
The easiest way (shown above) is just to use NSData and other foundation classes. NSData, NSString and NSImage all have methods to write their contents to disk. You just have to pass them an NSURL and they do the rest. They all also have class methods that attempt to initialize them from an NSURL on the disk. Thats seriously the easiest way. If you’re worried about file size and speed, use Grand Central Dispatch to put them on a background thread.
NSTableView
NSTableView and UITableView sound similar. But oh my. UITableView should have been called UIListView. It lacks the key component that makes tabular data… tabular. UITableView has no concept of Columns. That may seem like no big deal, especially if you only want NSTableView to show a single column. But its a bigger deal because UITableView has UITableViewCell that represents each line in the table. NSTableView has NSTableRowView and NSTableCellView. The RowView is responsible for handling (and drawing) selection of the entire row (which could encompass multiple columns and cells). The CellView acts more like UITableViewCell. That means that, similar to Windows in AppKit, the Table Cells in AppKit also have 2 objects that represent a single “cell.”
That being said, both UITableView and NSTableView share methods that are very similar for registering NIB’s for cell/row reuse. They also share very similar delegate methods for asking for cells. The difference is with NSTableView there are two of them. - (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row for the NSTableRowView and - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row for the NSTableCellView.
Legacy
Error Handling
Most of the iOS API’s I dealt with handled Errors via a callback closure. Maybe thats because on iOS I was mostly dealing with network requests? In any case, on OS X I had to get used to a different style of Error handling: using NSErrorPointer. I know iOS uses this as well, but It seemed far more prominent in OS X. The idea is that you create a reference to an optional NSError. Then you pass that reference in with the operation you are trying to perform (with the weird & inout Swift syntax). After the operation is complete that reference to the optional NSError could either be NIL or have an error. You then check if there is an error and handle the error, if its NIL, the operation is presumed to have succeeded.
NSTableView Row Height
In iOS 8, Apple introduced UITableView cell automatic height determination. Needless to say, they have not done this for NSTableView. With NSTableView you have to manually return the height of each cell in - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row. In my app, I draw a single cell offscreen. Then for each row, I change its contents, force it to layout and then grab its height and return it to the TableView. Its very similar to how I’ve done it before on iOS 7 projects. Its just a little bit annoying that OS X has had Autolayout for longer and has a lot more powerful CPU to do this kind of stuff for us and Apple still hasn’t implemented it on OS X.
Interface Builder
I have a lot of opinions on NIB’s. At first I thought they were really great. However, over time I have grown less in love with them. I could write an entire other post on NIB’s (and probably should). For this post, what is important is that this Mac OS X app I am working on was going to be my first App that did’t use NIBs. I was planning to draw everything in code and do all the auto layout in code. In iOS this would be easy. UIViewControllers can be instantiated without NIBs. UITableViewCell classes can be registered to UITableViews as classes rather than NIB’s, etc. In OS X, this is basically impossible. Creating an NSWindowController and an NSWindow without a NIB is possible, but you lose all the default behavior of windows. Default behavior includes remembering size, position, full screen state, minimized state, etc. Same with NSViewController. They can be created without NIB’s, but it requires a lot of work on your part. So what I ended up doing was creating all the NIB’s I needed to make all my Window Controllers and ViewControllers and just leaving them blank. So I have a bunch of NIB’s sitting in my project that just do nothing. I’m not super happy about that.
NSNotificationCenter
In iOS there are a lot of methods you override in order to respond to system events. Examples include rotation/size class changes, viewDidAppear, shouldPerformSegue, etc. OS X has a lot fewer of these built in methods and instead tends to use NSNotificationCenter to respond to events. This approach is a little more complex because you get an untyped UserInfo dictionary instead of a well defined method signature. But it also makes a lot of sense. As I said before, you have a lot more objects floating around in OS X and having pointers between them all would be a nightmare. So instead of the NSViewController having to know about the NSWindowController that it sits within, the NSWindowController can just register the NSViewController for Window lifecycle events like DidBecomeMainWindow. NSNotificationCenter is really good this way. Its also scary to think how easy it is in Cocoa to register some instance of some class to a new notification and then have the app crash when it gets notified. Also, remember to de-register notifications when an instance deallocates or else you’ll get random crashes all over the place. Aren’t unsafe unretained pointers fun? :)
NSAlert Gem
So a lot of this has been a little bit complainy. So, let me show you something that made me incredibly happy! NSError gets a bad rep by iOS developers. Its overly elaborate, it has indecipherable domains with indecipherable errors and it gets all messed up by third parties when they get their mits on it. Also, all the information has to be manually extracted by the developer and presented to the user. Well, OS X has an absolutely amazing NSAlert class. It has an initializer that just takes an NSError. NSAlert(error: NSError). Then when you present the NSAlert to the user, it does all the hard work of parsing the NSError and giving usable information to the user. Its absolutely incredible.


















