book covers

Indie macOS Dev Bundle, Swift 3 Ed..

Design solid applications and own your distribution process. Learn software architecture principles to make your Swift apps easier to maintain and extend. Set up your app for sale, with a trial mode and license generation all set. Including fully functional sample projects and code templates for your app.

Broken Help Menu Breaks Rather Unpredictably

Last week, I wrote about the Help menu bug that seems to “freeze” apps inasmuch as it makes them not handle any keyboard input.

The thing is, I cannot always reproduce this issue. I rebooted my system and just couldn’t break the behavior anymore. Which sounds an awful lot like running the system for a while and using it in a specific way might affect the Help menu’s behavior. But I don’t know which steps you need to take to reproduce this. It’s puzzling me. With Daniel Jalkut and a beta tester of my latest app having experienced the same issue, I know I’m not alone. Or crazy. Just clueless.

Plain Controllers to Ease the Burden of View Controllers

I like to see that Soroush and I think among the same lines. For one, that makes me feel less like a loony. And since he writes a lot more in recent months, it means my sloppy blogging schedule does not affect spreading ancient wisdom. In February, Soroush blogged about writing “Just Controllers”: service objects, usually reference types, that you use to orchestrate processes and offer a simpler API. Requiring a delegate to be plugged-in is one way to design a simpler API: the delegate procotol defines all the output events from the service object, or controller. That’s all there is going to happen as far as outside code is concerned. But on its inside, the controller can do the fanciest of things to accomplish its goal. These implementation details are hidden from code that uses these service objects. It’s object-oriented programming 101 all over again – encapsulation, information hiding, stuff like that. But, and this is a big But, the go-to massive view controllers in iOS apps are a symptom of not encapsulating properly. Go and delineate a few more boundaries inside your app. As with raising children, boundaries help your code grow properly, too!

Put Test Files Next to Production Code for Better Discoverability?

The Kickstarter engineering team posted an interesting proposal: put your XCTestCase files into the same directory and group as your production code. Do keep the separate target ownership, of course.

One clear benefit is directory tree or file management, and discoverability of tests for people browsing the code: In most cases, you have FooService in FooProject/FooApp/FooService.swift, and FooServiceTests in FooProject/FooAppTests/FooServiceTests.swift. With nested folders, you need to navigate around even more. I got used to this Xcode default. But I also got annoyed by this split at the root level when I browsed code on GitHub. Multiple tabs in a modern browser to the rescue, this was never that problematic, given the test directory is structured in a similar way. If not, good look finding the file you’re looking for. (I hate that the RxSwift project, which I often had to browse to look up an implementation detail, has files that contain only a path reference to the real file, similar to ../../Core/SomethingSomething.swift, making tests pretty hard to find).

I will try this approach with a library module that I develop as a sub-project of The Archive and report back.

High Sierra: Help Main Menu is Broken and Freezes Apps

If you run macOS 10.13 High Sierra, try not to use the Help menu. It will appear to freeze the app for which you invoke the Help menu: it will not accept keyboard input anymore, emitting the NSBeep “invalid action” sound in most circumstances.

The reason seems to be that the nifty Search bar inside the Help menu will acquire focus so you can search for a menu item or help book entry. This is taking away focus from the app window. You notice this when the blue focus ring around an active text field in the window goes away; instead, the Help menu’s Search bar obtains focus and the accompanying blue focus ring. All this is supposed to happen. This process just isn’t reverted for some reason since at least macOS 10.13.3, maybe earlier. When you click back into the app outside of the Help menu, the key window status will not be assigned back to the window you click on. That means keyboard input will not work at all. (As a non-native English speaker, I asked if “key window” was supposed to mean “window that responds to key events” a while back. While people agreed this wasn’t the intention, I still cannot help to think that way. The app has no key window, so key events are not handled, resulting in NSBeep.)

You can fix this inconsistent state yourself: open the Help menu again, then hover over an adjacent main menu entry, like “Window”. This will apparently close the Help menu in a different, proper way, unlike clicking back inside the main app’s window. Restarting the app works too. Cmd-tabbing out and back in does not help, though. (Backstage info: the Help menu will send willOpen when you tab back into the app, re-acquiring key status while not being on screen.)

I filed a bug report with Apple. See rdar://39374865 and make sure to clone the report so this is fixed sooner than later.

That’s a bad problem. As an app developer, your app will appear broken, so you need to fix this for your users even though it’s an Apple bug.

A working fix is to assign a NSMenuDelegate to the Help main menu item’s submenu (which consider to “be” the Help menu) and restore key status in the app. This is a pretty simple brute-force solution:

extension AppDelegate: NSMenuDelegate {
    func menuDidClose(_ menu: NSMenu) {
        if menu === NSApp.helpMenu {
            // Replace with more elaborate restoration:
            NSApp.mainWindow?.makeKey()
        }
    }
}

Setting the key window this way might actually result in a second menuDidClose(_:) call (after another menuDidOpen, by the way, which is odd).

Also, I found the window needs a reason to redraw afterwards to draw the blue focus ring where it belongs. Your app will forward key events to the current responder as expected, but if the firstResponder is a text field, the blue ring will not be visible immediately. I can live with this for now.

Also, I have yet to find out how multi-window apps or NSDocument-based apps behave. Is the right window restored? Do you want the mainWindow to become key? I’ll experiment with TableFlip and see what happens.

Today, Daniel Jalkut wrote about this, too, with a more elaborate approach to restore the actual state before the help menu was invoked.

A Visually More Accessible Disabled NSColorWell

I use the macOS color well to let users of The Archive tint vector icons in their favorite color. But setting NSColorWell.isEnabled hardly produces a visual change in the color editor.

Can you easily tell which is enabled and which is disabled if it weren’t for the checkbox?

Hint: the bottom one is enabled

The little frame color change is too subtle for my taste. I added a high-contrast strike-through line to my color well:

Screenshot of NSColorWell subclass with strike-through
Sometimes a line says more than a thousand shades of gray

Enjoy the code below:

class StrikethroughColorWell: NSColorWell {
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        if !isEnabled { drawStrikethrough(rect: dirtyRect) }
    }

    private func drawStrikethrough(rect: NSRect) {

        let lightLine = NSBezierPath()
        lightLine.move(to: NSPoint(x: 1, y: 0))
        lightLine.line(to: NSPoint(x: rect.width + 1, y: rect.height))
        lightLine.lineWidth = 2
        NSColor.white.set()
        lightLine.stroke()

        let darkLine = NSBezierPath()
        darkLine.move(to: NSPoint(x: -1, y: 0))
        darkLine.line(to: NSPoint(x: rect.width - 1, y: rect.height))
        darkLine.lineWidth = 2
        NSColor.darkGray.set()
        darkLine.stroke()
    }
}

The Archive Is Available Now

My next macOS app project has launched: It’s a note-taking app for macOS to help writers write more. In our typical German stoic way, we just call it The Archive.

Check out The Archive!

Its choice of features is closely tied to a few core principles: the user is more important than your app; plain text comes first, so avoid proprietary features and vendor lock-in, thus furthering replicability of the workflow in other apps. I guess we’ll write more about that in the future.

I’m collecting reactions from all around the web already on Pinboard.

Backstage Info

A few details of potential interest to programmers, taken from our Press Kit:

NSTextView’s Default Insertion Point and Selected Text Colors

NSTextView can be customized to display different colors for the insertion point (aka caret, aka text cursor) and for selected text. This is especially useful when you add themes to your editor and the default settings don’t fit anymore.

3 theme settings visualized

The default values are not exposed anywhere, so I had to log them and reconstruct convenient accessors to reset a text view if needed:

extension NSTextView {
    static var defaultInsertionPointColor: NSColor { 
        return NSColor.controlTextColor 
    }

    static var defaultSelectedTextAttributes: [NSAttributedStringKey : Any] {
        return [
            .foregroundColor: NSColor.selectedTextColor,
            .backgroundColor: NSColor.selectedTextBackgroundColor
        ]
    }
}

Launch of My Next App, The Archive

screenshot of The Archive

I spent the last 15 or so months developing this beast. If you follow my ramblings, you will know that I get to work every day at about 7 a.m. and work until about 5:30 p.m., including workouts and breaks. Since I didn’t use a time tracker like Timing for most of the time, I cannot say how many hours I really spent coding, sadly.

Anyway. The launch. The Archive will go live March 15th. I’ll publicize the app info page, soon.

And then I’ll spend some leisure time writing a postmortem. The beta testers have been a pleasure to work with, as always. It’s amazing how much love strangers are willing to give for carefully crafted products.

This closes a weird circle for me: my first Cocoa coding experience was adding Markdown preview to Notational Velocity in 2010, which I barely managed, having had zero understanding of Objective-C and how Xcode worked. Now I am able to create an app that exceeds NV’s capabilities all on my own. I’m very happy about this coding journey, and oh boy do I have a lot of cool stuff I am going to add after v1.0 goes live!


→ Blog Archive