This weekend was my first time ever needing to use NSPredicateEditor in one of my apps. I don’t know who else needs to know this, but just in case…
Maybe the documentation has disappeared online, or maybe it was only ever available via word-of-mouth fifteen years ago, but I lost about four hours the other night trying to figure out how to make the dropdown choices in my NSPredicateEditor show user-friendly names instead their actual key paths.
Here’s what I had…
And here’s what I wanted…
Unlike its parent class NSRuleEditor, NSPredicateEditor doesn’t ask a delgate for display values to use in place of the key paths. I had found arcane whisperings in dark, ancient parts of the web mentioning needing to use the formattingDictionary dictionary property, but for the life of me couldn’t figure out what to do with it.
Only after hitting my head on a wall for half a night did I finally stumble across this twelve year-old cocoa-dev mailing list post on the eighth Google results page behind a bunch of Chinese StackOverflow scraping websites with a solution.
On Oct 28, 2008, at 3:57 PM, Peter Ammon wrote: Here’s something that may help – there’s a method on NSRuleEditor- (NSData *)_generateFormattingDictionaryStringsFile. It gives you a strings file (as UTF16 data) appropriate for that control – write the data to a .strings file and then you can start translating it. That method should never be called in production code but it can be useful for generating the strings file.
God bless the Apple engineer that wrote that private API method because it absolutely does what it says on the tin. Add all of your NSPredicateRowTemplates as you would expect using your key paths, call that method, and you get output suitable for dropping into a strings file that NSPredicateEditor will read from and localize itself with. My strings file looks like this…
Hopefully the absurd amount of SEO keywords I stuffed into this blog post will help future developers find this info now that the old web is disappearing from Google.
After last year, I’m gonna try really, really hard not to bitch about Apple if at all possible in 2020. But what good are New Year’s resolutions if you don’t break them before the end of January? So, here’s a small bug that for the life of me I can’t figure out along with my solution.
I have a number of what I call “working folders” on my Mac. They’re the main folders where my projects, source code, temporary files, and other in-progress files live. I’m constantly navigating to them, dragging files in and out of them, etc. In no particular order they are…
~/Downloads
~/Dropbox
/Dropbox/_Inbox
~/src
~/tmp
~/Downloads is exactly what you expect. It’s where everything from my web browsers go.
I live in my synced cloud folders. I was a paying Dropbox customer from 2008 until 2018, then switched to Google Drive because I was also paying for Google Photos and decided to cut costs, and then had a quick dalliance with iCloud Drive in the first half of 2019. But, it turns out every sync service is horrible and awful except for Dropbox. So, I’m happily back on the wagon – even after they raised their prices and destroyed their Mac app. That means I am constantly diving deep into ~/Dropbox to find reference material, share files, etc.
I went paperless over a decade ago. Every physical paper worth remembering or that I might possibly want to reference in the future along with all of my kids’ artwork and schoolwork gets captured in Scanbot (RIP my beloved ScanSnap) and automatically uploaded into /Dropbox/_Inbox. It’s the temporary staging area for all the documents and files I need to process before I rename and sort them into their final storage locations.
~/src is, you guessed it, where all of my source code lives. Everything is in a git repo, so there’s no need for anything in this folder to be in ~/Dropbox.
And, finally, ~/tmp is a sort of working directory where I put files that don’t need to sync across machines and also large files that I don’t need long-term but can’t put on my Mac’s ~/Desktop because that syncs to iCloud and I don’t want to waste the bandwidth.
Anyway, the point of all the above descriptions is to make the case that those are the five folders I deal with all the time if not literally every five minutes. And blame it on my crazy personality, but I hate wasting time clicking around in Finder windows. I need fast access to them.
For a number of years the solution was simple. I just added aliases to each of them in my Dock.
That let me open them with a click, or even navigate into their subfolders. I could also drag and drop files into and out of them. It was great.
But then sometime around the later stages of Mojave, the folders would just disappear. I’m serious. I’d reboot my Mac and they’d be gone from the Dock. So, I’d add them back. And then a day or two later – even though I hadn’t yet rebooted a second time! – they’d vanish again. I wasn’t running Mojave betas, and the problem has persisted through today running stable Catalina 10.15.2.
When weird things happen I’m usually pretty good at tracking down if not the cause then at least what conditions trigger the bug. But this one has had me stumped for the better part of a year. I’m so confused I’m not even mad.
I tried to work around the problem by adding folder aliases on my ~/Desktop, but that just wasn’t as convenient. One afternoon I thought I was especially clever and tried to write a shell script that manipulated one of the Dock’s .plist preference files to automatically re-add the folders when my Mac restarted. But I never got that to work reliably.
So, I started looking for any 3rd party apps that might help. I tried Yoink, Dropzone, and a few others, but none were really quite what I wanted.
I’ve also been a heavy user of Alfred ever since Quicksilver gave up the ghost, so I know about its “Quick File Search mode” setting in Preferences. That’s great for getting to obscure files/folders quickly, but it’s just not fast enough for me for these five particular folders. For some reason having to hit the apostrophe key to enter Alfred’s file search mode just screw ups the next few letters I type every time and I always end up making typos and regretting my life choices.
So, I did what I almost always do when I face a situation of deep despair on my Mac. I reached for the greatest Swiss Army knife of them all – Keyboard Maestro – and came up with an incredibly lo-fi solution that isn’t as feature rich as what those other apps offer or as convenient as Dock folders (when they don’t disappear), but it works for me!
It’s a Keyboard Maestro macro that I’ve assigned a global hotkey to. When invoked, it displays a list of those five folders. I can arrow up/down to select one, or type to filter, and then press return to select. The picker window goes away and Keyboard Maestro opens a new Finder window with that folder.
Here’s the picker window…
And a quick video of it in action…
And the macro itself…
The whole thing is stupid simple. But I’m happy with it. Especially since the bug this is designed to work around is over a year old and, well, you know.
You can download the macro and import it into KeyboardMaestro here.
Quick question. Take a look at these two screenshots…
and
That’s my Mac right now at work with Spark and Slack running fullscreen in Split View.
In the first screenshot, one of the apps has focus. And in the second screenshot the other app has focus.
Can you tell which is which?
I’ve never been a big fan of Spaces or full screen on macOS, but I’ve been giving them a try again recently just to see if I can improve my workflow. And after a day or two of having a dedicated space for Slack + email, I found it maddening.
It turns out that I really enjoy having my noisy, communication windows segregated from the apps and windows I actively use to do work, but every time I’d switch to that space and either try and type into Slack or use the keyboard for navigation, I’d invariably realize the wrong app has focus.
Why? Because there’s no way to tell which is which.
Ok, technically that’s not true. You can tell. I’ve highlighted the difference below…
When that little 4px by 64px, gray bar is visible it means Slack is focused. Or something. I don’t know. Probably no one does.
Like I wrote about on iOS a few months ago, there’s been a trend in the industry ever since the iOS 7 redesign to get rid of UI affordances – or as Apple puts it, “subtle and appropriate” “adornments”. I don’t know if these changes are deliberate, due to a lack of empathy for the user, or just inexperience. But as apps deviate further and further away from the HIG with custom UI, whether for design reasons or in the pursuit of a mythical, cross-platform code base that management thinks will cost less, we lose the benefits of a well reasoned platform that was formerly easy to work with and a joy to use. And if we’re gonna go down that road, then let’s just give up and put everything inside a web browser. At least then we can see which tab has focus.
Last May I wrote about how I was keeping alive a persistent SSH tunnel back to my home network. Specifically, it connects to an iMac Pro that I use as a home media/automation server. I frequently need to screen share, view a web service hosted on it, or just access its command line. It also serves as our build server at work. An SSH tunnel is basically a poor man’s VPN. And I feel more secure about using that then just outright opening up ports in my firewall.
Anyway, nine months later, it’s proven to be a great solution. However, there is an occasional quirk.
The launchd job that controls the tunnel is set to automatically restart the process if it dies. And, sure, when it does die launchd does the right thing and restarts it. But in some situations the tunnel won’t actually terminate when it fails – it just hangs or becomes unresponsive. I was able to reproduce the issue a few times by making my WiFi flake out. So, I turned off the iMac’s wireless and hardwired it into my router. That was a big improvement, but I still saw the occasional oddness and found myself locked out a number of times when trying to connect back from work, the coffee shop, etc.
Thankfully, before I wasted too much time debugging, I recognized this problem for what it was: a giant rabbit hole.
I knew damned well that I could absolutely lose (and enjoy!) a whole evening or entire weekend running tests, debugging, and trying to figure out the cause of the intermittent failures. I’ve done it a thousand times with other piddly little problems. But, as Oliver Wood might say, “I am wiser now.” I’ve got better ways to spend my time. So, let’s just fix it.
My thinking went like this:
When the network goes down, the tunnel will close, the ssh process will quit, and launchd will keep restarting it until it connects again. That part works fine.
The problem is figuring out when the connection has stalled or experienced some other type of unresponsive fuck-up. How do I detect that?
Me! I’m a smart human. It should be me detecting the issue instead of wasting hours trying to find a way to make the computer catch it. When I try and connect to the tunnel and it doesn’t work, that means something is broken. And since I only care if it’s broken when I’m actually trying to use it, that’s when I need to fix it. Automatically detecting any and all failures simply isn’t necessary.
So, when I notice the tunnel is down, how do I tell my iMac to kill the process and let launchd restart it when I don’t have access to the machine in the first place? I could just kill the process automatically on a set schedule. But that might interrupt me when I was actually using it. Also, if it were down, I’d have to wait until the next interval for it to come back up. I need to send a command while remote and without having access to the machine – preferably in a way that I can also do from my phone.
<Spidey sense starts tingling>
Hazel! Dropbox! And a small shell script! My three favorite tools.
First, let’s figure out how to kill the existing (stalled) ssh process. From my original blog post, you can see that launchd runs this command to start the tunnel:
/usr/bin/ssh -N imacvpn
So, we can figure out the pid of that process by greping for the imacvpn keyword like this:
ps -ax | grep imacvpn | head -n1 | cut -f1 -d " "
That’s a four part command
First, it lists all the running processes under my account.
Filter to just those that contain the name of the SSH tunnel. That will typically return two results. The real SSH tunnel command and also the grep command itself.
So, grab just the first line which will be (should always be?) the real tunnel process.
Split that line of text using a space as the delimiter. The first token will be the pid which we’ll use to identify and kill the tunnel.
With that pid, we can then kill the process like this:
That’ll find the ID of the SSH tunnel process, kill it, then launchd will take over and start it back up again.
Great. But how to make that script run on command? That’s where Hazel and Dropbox come in.
If you don’t know Hazel, go here. What we’re going to do is tell Hazel to monitor a specific folder inside my ~/Dropbox/. Whenever a new file is added to it, Hazel will detect the change, delete the file (since we don’t actually care about the file), and then run the above shell script.
Here’s how the configured Hazel rule looks:
Basically, we’re using Dropbox as a way to trigger a filesystem change that Hazel will detect and run whatever command we ask it to.
It’s great because if I’m on my computer at work or my laptop in a coffee shop, I can just save a blank text file into that folder. Dropbox will sync it to my iMac. Hazel will see the change and run the script.
And if I’m somewhere with just my phone, I’ve found the easiest thing to do is open up Photos.app and save a photo into Dropbox.app using Files.app via the system share sheet.
So that’s the dumb solution to my problem that I came up with. I’m still not sure what the actual technical reason is for the SSH tunnel occasionally crapping out on me. I’m just glad I’m to the point in my nerd existence where I can be happy applying a fix and not caring about the real underlying issues that don’t concern me.
The part with grep -v is a negative grep (find all lines that do not match “grep”)
This way it doesn’t matter which of the two results is listed first.
That’s one of my favorite things about shell scripts – there’s a million approaches to every problem. The above is a great way to make sure the correct pid is chosen. Thanks, Leo!
Since then, I’ve helped shipped a huge redesign to the app at my 9-5 job as well as two major updates to VirtualHostX and CommandQ. Complicating matters, VHX Pro is my first product ever to use a subscription pricing model. So, in addition to all the dev work that went into the app itself, I also had to write the server infrastructure to handle recurring billing, etc. I’ve been busy.
Nonetheless, I continued tinkering with my finance app all year. It’s basically my white whale at this point. And I’m happy to say it’s reached a point where I rarely if ever log into my bank’s website. Everything is done through my app.
But it’s not nearly ready yet. The UI and feature set is still too much in flux. However, while experimenting with different ways of managing and forecasting a budget within the app, I decided to prototype a few ideas as small, standalone Mac apps so I could build and test them in isolation. One of them took hold and became incredibly useful to me in real life as my wife and I made adjustments to our finances and tried to repair the damage from 2018.
I shared the app with a few friends and received some encouraging feedback. And then last month, with the end of the decade just a few weeks away, Twitter blew up with people posting about how they spent the 2010s and what they accomplished. I sure had a hell of a decade. But I wasn’t in the mood for reminiscing. I wanted to look ahead. So, I got the idea to put something new out into the world on January 1st and turned to my little budgeting app as it was the closest thing I had ready to go.
The app isn’t meant to be an identical mirror of your bank statement. (That’s what my other, larger finance app is for.) Rebudget is a tool for planning and forecasting. Use it to see if your money is heading in the right direction. Use it to see if you can afford to take on a new car payment this Summer or if you should wait till next year. Figure out if you need to ask your new freelance client for a 50% deposit up-front or if you have enough cushion to wait for a final payment when the project is finished.
And that’s kind of the idea. If you’re already financially secure and never stress about your bank account, great. Rebudget isn’t for you. But if you freelance for a living or have other income that doesn’t arrive on a strict once or twice a month schedule – or if you’re recovering from a financially disastrous year where it seemed like everything was spiraling out of control, I think Rebudget can be a great way to plan or at least sanity check your finances. It’s certainly helped me in recent months.
I built the app to work the way I think. And that meant focusing on flexibility. I like to work with and visualize different scenarios at once. So rather than making Rebudget a typical shoebox Mac app with a canonical datasource, I went with our good friend NSDocument. That means you can have as many budgets as you want. Open them up side-by-side in different windows and compare how certain financial decisions effect things eighteen months from now. Or, quickly toggle a recurring bill on or off to see if it’s worth cutting that expense.
I have an early iOS version in the works, but until then I still wanted a way to have my data somewhat available on the go. So, I added an option to sync your bills and their due dates with Reminders.app. That way your bills show up on your calendar and you get alerts when they’re due. Also, if you check off a bill in Reminders, Rebudget sees the change and marks that bill as paid in its own database.
Here’s a quick, two-minute demo.
That’s Rebudget. I wanted to put a brand new thing out into the world to begin the new decade, and that’s what I came up with. I find it useful. I hope you enjoy the app, too.