Tanagram Roadmap: September 2022
This is my eighth monthly public roadmap update for Tanagram development (see previous updates here). I’m publishing this update to document my progress and hold myself accountable, and also provide a place to share some thoughts about what I plan to work on next.
Tanagram remains a nights-and-weekends project. My progress pace over the past month has averaged about 1 workday per week, with some weeks being higher and other weeks being lower.
Would you like to receive these updates over email? I’m also publishing these to my Buttondown newsletter. Click here to subscribe.
Results: August 2022
I’ve been building demo-able (read: powered by hard-coded data) UIs to get a better feel for my initial designs. A month ago, I had this little thing:
Now, I have a lot more, and I’ve learned a lot about how to use AppKit:
Each of the panes are an “item”, an instance of several built-in types. Each item pane has a contextual action bar, showing the actions that can be performed on or with that particular item. There’s also a global action bar with app-wide actions; it will also support open-by-ID: you’ll be able to paste in the ID of any item and open a pane for it1.
A little more than a decade ago, I taught myself to code with C, Objective-C, and the nascent iPhoneOS SDK2. After switching to building webapps around 2014, I’m coming back to Apple’s development platforms with a hint of nostalgia and a wallop of bewilderment. Here’s a smattering of things I’ve learned recently, mostly from trying things and seeing what happens:
-
init(frame:)
is the default initializer forNSView
, but (in an autolayout world, at least) the providedframe
doesn’t seem to do much. I used to spend a bunch of time and code calculating what the frame should be, but usingNSRect.zero
seems to work in most cases. -
I struggled with
NSTabView
for a while: it seems to silently add some autolayout constraints, despite me turning offtranslatesAutoresizingMaskIntoConstraints
. These constraints cause an ambiguous layout, and I wasn’t able to get to them to manually remove or modify them. -
NSButton
can’t store much information about what the button represents (e.g. “this button operates on itemobj_123
“). It has atag
field, but that can only store an integer. If you wanted to store a string ID or anything more complicated, you’ll have to create a whole subclass. -
On the web, CSS affords a broad set of visual attributes (e.g. backgrounds, borders, padding, and rounded corners) to every view. That’s not the case for
NSView
— I had to create subclasses specifically to draw borders and rounded corners, and doing this drawing requires lots of manual code (e.g. manually creatingNSBezierPath
s), and this code can only be called fromdraw(rect:)
. -
I wanted the action bar (both the global one and the item-specific ones) to behave like Safari’s navigation bar: it’d present a list of options below the bar that changed based on the user’s input, but keyboard focus would remain with the input field so the user could keep typing. I originally tried doing this with
NSMenu
, but soon realized this wouldn’t work becauseNSMenu
wanted to take keyboard focus. Instead, I had to create the whole view from scratch and try to make it look like a native menu. Luckily, I found a link to an old sample project. It was last updated ten years ago, but to my pleasant surprise, it still compiled and Just Worked. -
NSTableView
doesn’t render its headers when you add the table view as a subview directly, but they magically appear if you add the table view to anNSScrollView
. -
Setting
edgeInsets
onNSStackView
instances works along the stack view’s primary axis, but don’t seem to do anything along the other axis. -
A few times, I would create a view, programmatically add some subviews (all sizes were defined by autolayout), and then want to want to know the resulting size of the parent view.
view.frame
would return0
or some obviously-wrong number. For a while, I had no idea how to make AppKit refresh the layout so I could get accurate sizes. Eventually, I found a StackOverflow answer that mentioned theNSView.layoutSubtreeIfNeeded()
method, which turned out to be exactly what I needed.
My overall impression is that AppKit is very flexible and lets me muck around and do anything, but there’s also some magic behavior and, to be effective with it, I need to know the (non-obvious) incantations and places to poke for various results.
Roadmap: September 2022
I’ll spend September building more UI to showcase some specific use cases I have in mind. Specifically, I’ll be working on:
- Query ItemView
- Datum ItemView (described in this post)
-
Using a datum in a command (e.g. as an API key)
- Bidirectional link between a datum and a command
- Creating an event type from a command and linking them on the canvas
- Creating an event handler from an event
- Canvas zooming