Latest Posts

Is the Mac mini (Mid 2011) a good developer device in 2017?

Short answer: No.


My trusty Mac mini served me well for years thanks to its 8 GB RAM and the 256 GB SSD. An SSD really helped make it faster back in the day. But with Xcode 9 and macOS 10.13 “High Sierra”, the machine begins to stutter.

I think it’s the CPU not really liking what I demand from it. Xcode 9 builds take a ton longer and the UI is less responsive. Sure, Xcode 9 sports a UI overhaul and might not be as performant as it could be, yet. That doesn’t help with the compile times which, for my current project, now take up to 5mins; that’s not even a fresh build.

So if you want to develop apps with Swift 4 and on the latest macOS, better get a more recent device. I don’t know which to recommend; the MacBook Pro is traditionally a good fit, but an iMac (or upcoming iMac Pro with its uncountable amount of CPUs and endless RAM) might be a good fit, too. Listen to Under the Radar ep. 89 for a more in-depth discussion by Marco Arment and David Smith.

I’d prefer a faster Mac mini because I have a nice-looking screen already and work at my desk 99.99% of the time anyway.

Inject ReSwift Middlewares from Different Project Targets During Runtime

Say you extract the ReSwift-based state module with all your app’s state types, reducers, and middleware into its own framework target. Until you add middlewares to the mix, it’s easy to keep this coherent.

Injecting side-effects through middleware is not easy to do, though, as long as you provide the Store instance from your state module. The store takes an array of Middleware upon initialization. They cannot be changed later. The state module would then depend on your app module with all its network request services and whatnot. That’s the wrong way around, also resulting in a circular dependency. Instead, you have two options to add side-effects that are inherently part of your app’s module, not of the state module:

  1. Create the store inside the app module, not in the state module;
  2. Add a general-purpose side-effect middleware that exposes mutation functions to the outside world.

The latter can look like this:

SideEffectRegistry.instance
    .addBeforeReducers { (bananaAction: BananaAction) in 
        Swift.print("Banana action passing through: \(bananaAction)") }

As a bonus (and risk!), this approach allows you to change which middleware are active during runtime. Keep in mind that ReSwift is designed not to allow this, so proceed with caution.

The closure is of type SideEffect. I found it beneficial to not have SideEffect take just any ReSwift.Action by default, because I usually conditionally check for one specific type, not multiple. So I lift the action type filtering to a generic constraint:

public typealias SideEffect<A : Action> = (_ action: A, _ dispatch: DispatchFunction) -> Void

Now you cannot create an array from these function bodies anymore; also, though the Action subtype is now reified as a generic constraint, the actual cast from Action to A has to go somewhere. I put it into a AnySideEffect box:

struct AnySideEffect {
    let boxedSideEffect: (Action, DispatchFunction) -> Void

    init<A: Action>(_ base: @escaping SideEffect<A>) {
        self.boxedSideEffect = { action, dispatch in
            guard let castAction = action as? A else { return }
            base(castAction, dispatch)
        }
    }
}

Now you can have an [AnySideEffect] array and pass any Action through. Only matching actions will be passed to interested middleware.

Heads up: This closure-based approach can allow removing all registered SideEffect, but not specific instances. For that, instead of a typealias‘d closure I’d use a reference type and check for reference equality (===) to remove/deactivate them.

Update 2017-09-29: I renamed the types a bit, from ApplicationMiddleware to SideEffect, etc.

The Full Implementation

/// - parameter action: The expected action to perform a side effect on.
/// - parameter dispatch: Default dispatch function into the current store.
public typealias SideEffect<A : Action> = (_ action: A, _ dispatch: DispatchFunction) -> Void

struct AnySideEffect {
    let boxedSideEffect: (Action, DispatchFunction) -> Void

    init<A: Action>(_ base: @escaping SideEffect<A>) {
        self.boxedSideEffect = { action, dispatch in
            guard let castAction = action as? A else { return }
            base(castAction, dispatch)
        }
    }

    func process(action: Action, dispatch: DispatchFunction) {
        boxedSideEffect(action, dispatch)
    }
}

public class SideEffectRegistry {
    public static var instance = SideEffectRegistry()
    private init() { }

    internal fileprivate(set) var sideEffects: [AnySideEffect] = []

    public func addBeforeReducers<A: Action>(sideEffect: @escaping SideEffect<A>) {
        sideEffects.append(AnySideEffect(sideEffect))
    }

    internal func process(action: Action, dispatch: DispatchFunction) {
        sideEffects.forEach { $0.process(action: action, dispatch: dispatch) }
    }
}

let sideEffectsMiddleware: Middleware<AppState> = { dispatch, getState in
    return { next in
        return { action in
            SideEffectRegistry.instance
                .process(action: action, dispatch: dispatch)

            next(action)
        }
    }
}

Show a fat iOS-Style Insertion Point in NSTextView

I have no clue why my previous attempts at customizing drawInsertionPoint(in:color:turnedOn:) always produced visual glitches. I really tried a lot of different ways. But it turns out you don’t have to do that much, really:

class MyTextView: NSTextView {
    var caretSize: CGFloat = 4
    
    open override func drawInsertionPoint(in rect: NSRect, color: NSColor, turnedOn flag: Bool) {
        var rect = rect
        rect.size.width = caretSize
        super.drawInsertionPoint(in: rect, color: color, turnedOn: flag)
    }

    open override func setNeedsDisplay(_ rect: NSRect, avoidAdditionalLayout flag: Bool) {
        var rect = rect
        rect.size.width += caretSize - 1
        super.setNeedsDisplay(rect, avoidAdditionalLayout: flag)
    }
}

Adapted to Swift from a Gist by koenbok.

That’s all it takes. Marvelous!

Update: So it turns out that the drawing works just fine in a test project, but I end up with 1px-wide insertion points/carets/I-beams when I click with the mouse or press up/down arrow keys. Left and right work fine. I wonder what’s wrong with this. Isn’t it supposed to be a rather simple customization?

How to Use NSGlyph in Swift

I couldn’t find a simple answer on the web at first, so here’s my take for Googlers.

When you need NSGlyph (which is a UInt32), you probably want to use NSATSTypesetter.insertGlyph(_:atGlyphIndex:characterIndex:) or NSGlyphStorage.insertGlyphs(_:length:forStartingGlyphAt:characterIndex:) which in turn is implemented by NSLayoutManager. But the useful glyph types to use like NSControlGlyph are Ints. How do you get a NSGlyph-pointer from these?

Thankfully, in Swift you can satisfy UnsafePointer<NSGlyph> in two useful ways:

  1. Pass a reference to a mutable variable with the & prefix, like:

     let glyphIndex = ...
     let charIndex = ...
     var glyph = NSGlyph(NSControlGlyph)
     layoutManager.insertGlyphs(&glyph, length: 1, forStartingGlyphAt: glyphIndex, characterIndex: charIndex)
    
  2. Pass an array of immutable values that is going to be kept alive for long enough, like:

     let glyphIndex = ...
     let charIndex = ...
     layoutManager.insertGlyphs([NSGlyph(NSControlGlyph)], length: 1, forStartingGlyphAt: glyphIndex, characterIndex: charIndex)
    

Since the exemplary insertGlyphs method call uses a plural-s, it’s pretty straightforward to use an array once you stop worrying about the question “where do I get a pointer from?”

Setting the NSTextView Line Height in a Beautiful Way

In the original post about a cheap way to set the line height in a text view to, say, 150%, the result kind of worked but didn’t look that cool. One issue is that the extra line spacing was exclusively added at the bottom.

With the following solution, you’ll get a proper line height with tastefully aligned insertion point and baseline and all.

The TextKit/Cocoa Text thing we are going to change is the lineFragmentRect. TextKit is immensely powerful, but also complex and not easy to start out working with. To increase any given lineFragmentRect, implement NSLayoutManagerDelegate like this:

class ViewController: NSViewController, NSLayoutManagerDelegate {

    let lineHeightMultiple: CGFloat = 1.6
    let font: NSFont = NSFont.systemFont(ofSize: NSFont.systemFontDefaultSize())
    
    public func layoutManager(
        _ layoutManager: NSLayoutManager,
        shouldSetLineFragmentRect lineFragmentRect: UnsafeMutablePointer<NSRect>,
        lineFragmentUsedRect: UnsafeMutablePointer<NSRect>,
        baselineOffset: UnsafeMutablePointer<CGFloat>,
        in textContainer: NSTextContainer,
        forGlyphRange glyphRange: NSRange) -> Bool {

        let fontLineHeight = layoutManager.defaultLineHeight(for: font)
        let lineHeight = fontLineHeight * lineHeightMultiple
        let baselineNudge = (lineHeight - fontLineHeight) 
            // The following factor is a result of experimentation:
            * 0.6

        var rect = lineFragmentRect.pointee
        rect.size.height = lineHeight

        var usedRect = lineFragmentUsedRect.pointee
        usedRect.size.height = max(lineHeight, usedRect.size.height) // keep emoji sizes

        lineFragmentRect.pointee = rect
        lineFragmentUsedRect.pointee = usedRect
        baselineOffset.pointee = baselineOffset.pointee + baselineNudge

        return true
    }
}

Note that this will increase the line height for all lines – except the last trailing newline or the height of the blinking insertion point in an empty document.

When you have an empty text or add a trailing newline character to your text, the insertion point is actually outside the very text container you know and love. NSLayoutManager has a extraLineFragmentRectContainer that takes care of the extraLineFragmentRect – which is used for the last (or only) empty line in a text. You need to increase that to a similar value.

If you have trouble wrapping your head around this concept, think about it this way: a newline character is not actually a glyph. It is not drawn. On top of that, a "\n" does belong to the line it is put on, but then you do not really have a line following after that until you type.

The following string:

The first line\nand the second,\nbut after this, there's no 4th.\n

… will be treated by text editors like:

The first line↩︎
and the second↩︎
but after this, there's no 4th↩︎

… where the last line break is a character of the 3rd line, but renders as:

The first line
and the second,
but after this, there's no 4th.

These 3 lines of text are supposed to illustrate that the trailing newline character is a character of the last line with text in it; if you are in a text editor and put the insertion point after the last \n-newline, you’ll be taken to a 4th line of text that doesn’t exist in the document. It’s merely a user experience thing.

So the layout manager cheats. When there’s a trailing newline character (or empty text), it appends the extraLineFragmentRect. In that rect, your insertion point blinks.

To change the height of the extraLineFragmentRect to suit a larger line height multiple setting, there are two places to intervene:

  1. NSLayoutManager.setExtraLineFragmentRect(_:,usedRect:,textContainer:) itself can be changed to call super with a different line height.
  2. Subclassing NSTypesetter and suggesting a larger rect in every occasion you would call NSLayoutManager.setExtraLineFragmentRect.

I have no clue about NSTypesetter, so the next best thing I can control (and thus suggest you do as well) is overriding setExtraLineFragmentRect.

class LayoutManager: NSLayoutManager {

    var lineHeightMultiple: CGFloat = 1.6

    private var font: NSFont {
        return self.firstTextView?.font ?? NSFont.systemFont(ofSize: NSFont.systemFontSize())
    }

    private var lineHeight: CGFloat {
        let fontLineHeight = self.defaultLineHeight(for: font)
        let lineHeight = fontLineHeight * lineHeightMultiple
        return lineHeight
    }

    // Takes care only of the last empty newline in the text backing
    // store, or totally empty text views.
    override func setExtraLineFragmentRect(
        _ fragmentRect: NSRect, 
        usedRect: NSRect, 
        textContainer container: NSTextContainer) {
        
        // This is only called when editing, and re-computing the 
        // `lineHeight` isn't that expensive, so I do no caching.
        let lineHeight = self.lineHeight
        var fragmentRect = fragmentRect
        fragmentRect.size.height = lineHeight
        var usedRect = usedRect
        usedRect.size.height = lineHeight

        super.setExtraLineFragmentRect(fragmentRect, 
            usedRect: usedRect, 
            textContainer: container)
    }
}

So you end up with a NSLayoutManagerDelegate and a NSLayoutManager and essentially put similar calculations in two different places. You could argue that since we’re working with a NSLayoutManager subclass now anyway, we could override setLineFragmentRect (note the missing “Extra”), too, and have both settings in one place.

I like to keep the dedicated delegate methods alive as long as I can, though, and find the baseline offset setting very convenient for our purposes.

And that’s about it!

If you never dipped your toe into the intricacies of TextKit, I guess you fell like I did: not happy about the many things you have to know for such a simple effect. “If I have to perform this kind of calculation and conform to that kind of complicated delegate methods to increase the line height,” you might ask, “what will I have to go through to implement really fancy features?” – And I really sympathize with that irritation. There’s so many intricacies to know! After 2 weeks of fighting with TextKit, I now have a better overview but still fail to guess which component does what; we’ll see how deep I have to dive and what I’ll find out in the upcoming weeks and months.

Typewriter Mode: Adding Overscrolling to the Text View

Typewriter modes depend on the feature that you can scroll farther up and down than usual. You need extra whitespace, most of the time in both directions. Let’s start with “overscrolling” to understand what we need.

Apps Without Overscrolling

Regular text views show additional bottom whitespace only until you fill it with text. Take TextEdit, for example. You can start to type at the topmost edge of the text view and the rest of the window is blank.

Plenty of whitespace at the bottom in empty TextEdit documents

But as soon as you hit the visual bottom of the view, you cannot scroll up to get the whitespace back. The enclosing NSScrollView only allows you to scroll until the last line of text comes into view.

TextEdit stops scrolling when the last line becomes visible

When you compose a text, looking at the very bottom of your screen most of the time can feel stupid. That’s why typewriter scrolling became so popular: you can type but the edited line stays visually centered on the screen.

Apps With Overscrolling

Now most apps with typewriter scrolling also have overscrolling. But some, like Byword, don’t allow you to scroll beyond the confines of the document until you activate the typewriter mode.

TextMate is a bit different. It allows you to scroll down as much as you like, up until the last line is at the very top of the view.

TextMate allows you to scroll down until the last line of text is at the top

TextMate doesn’t feature a typewriter mode, which basically is overscrolling + locked insertion point location, though, so you have to adjust the scrolled position for yourself.

Implementing Two-Sided Overscrolling in a NSTextView

To tell the NSScrollView that you want to scroll beyond the confines of the text, you need to tell it that its documentView is larger than it actually is. I found increasing the vertical textContainerInset to work pretty well:

class TopAndBottomOverscrollingTextView: NSTextView {
    func scrollViewDidResize(_ scrollView: NSScrollView) {
        let lineHeight: CGFloat = 14 // compute this instead
        let overscrollInset = scrollView.bounds.height - lineHeight
        textContainerInset = NSSize(width: 0, height: overscrollInset)
    }
}

Now you have increased the text view’s total size by twice the container height, so you have extra space at the top:

… and at the bottom:

One-Sided Overscrolling

The previous result is a good base to get started implementing a typewriter mode. Without a typewriter mode, overscrolling beyond the top of the document is pretty weird, though. You cannot have a textContainerInset with asymmetric values, so you can only set a vertical inset that affects both edges. But you can move the container around. So you’ll end up doing:

  • Set the inset to 1/2 of what you want,
  • move the container up the same value,
  • so that you end with exactly what you want at the bottom.
class BottomOverscrollingTextView: NSTextView {
    func scrollViewDidResize(_ scrollView: NSScrollView) {
        let lineHeight: CGFloat = 14 // compute this instead
        let offset = (scrollView.bounds.height - lineHeight) / 2
        textContainerInset = NSSize(width: 0, height: offset)
        overscrollY = offset
    }
    
    var overscrollY: CGFloat = 0

    override var textContainerOrigin: NSPoint {
        return super
            .textContainerOrigin
            .applying(.init(translationX: 0, y: -overscrollY))
    }
}

Experimentally, I found this computation to work better:

let offset = floor((scrollView.bounds.height - 14) / 2) - 1

It gets rid of half pixels and then adds a tiny extra offset that’ll help keep the last line in sight when you overscroll. If the last line is obscured even 1 pixel, the scroll view will scroll up to pull it into view.

So the bottom overscrolling works as expected:

If you look closely, you’ll see there’s one extra pixel above the text selection

While the top is clean and looks like usual text views:

Implement scrollViewDidScroll Callback Notification

One thing I skipped so far: the origin of scrollViewDidScroll(_:). You have to sign up for notifications for this kind of event, for example in your view controller:

class ViewController: NSViewController {

    @IBOutlet weak var scrollView: NSScrollView!
    @IBOutlet weak var textView: NSTextView!
    
    func viewDidLoad() {
        super.viewDidLoad()
    
        scrollView.contentView.postsBoundsChangedNotifications = true
        NotificationCenter.default
            .addObserver(textView, 
                selector: #selector(scrollViewDidScroll(_:)), 
                name: .NSViewBoundsDidChange, 
                object: scrollView.contentView)
    }
}

That’s all you need to get rolling.

Next Steps

This is already some achievement in terms of making text editing more user-friendly because now people don’t have to stare at the bottom of the screen (or window) all the time. Still, without the typewriter scrolling that maintains the extra whitespace even when typing, there’s room for improvement.

Scroll NSScrollView Programmatically Without Showing the Scroller Knobs

I am currently working on a typewriter mode text view. Even though this is a very popular trend for a couple of years, I couldn’t find any open source component for this feature. I assume those who figure this out keep it a secret. If that is the case, it doesn’t make that much sense nowadays anymore since every 3rd note taking app or so has such a feature already.

My naive implementation attempts got better over the past days. But keeping the line the user is typing in at the same position relative to the window all the time involves performing a scroll every now and then. But then the scroller knobs show. I tried to circumvent this for a while now, with subclasses and by overriding private API. Here’s what ended up working:

class TypewriterTextView: NSTextView {
    // ... all the really interesting stuff ...
    func typewriterScroll(to point: NSPoint) {
        self.enclosingScrollView?.contentView.bounds.origin = point
    }
}

Call typewriterScroll(to:) instead of NSView.scroll(_:) to change the scrolled position without flashing the scroller knobs.

Up until a few minutes ago, I would’ve vowed that I tried this and it didn’t work a few days ago. But it does work. Go figure.

Well, power to the people! Guess I can show you a functional example, soon, with the rest of the component explained.

macOS Storyboard Outlet-Like Connections Between View Controllers

Transitioning from Nibs to Storyboards poses a few challenges. One of them being creating outlets from a parent view controller to a child view controller. There is no such thing.

You could say this is the new idiomatic way to create interfaces, but I don’t quite agree with the consequences. How do you pass data to a view controller 4 levels deep in the hierarchy? It’s not obvious. And there are no “embed” segues like you have them on iOS to obtain references.

Just as I have beef with Segue’s, I don’t want my view controllers to be the central point of control. I want to tell them what to do. Eventually, I guess I’ll have to settle for a programmatic view, but that day is not today.

Turns out that NSViewController now sports a childViewControllers: [NSViewController] property which you can use to query for parent–child-relations.

As usual, my custom NSWindowController subclass also was the façade for the whole view hierarchy, implementing all view-related protocols to forward the display(foo:) commands to the child view controllers. That made the transition from Nib to Storyboard very easy, in fact, since there’s just a single point of change. High locality of change for the win! – If only the outdated child view controller outlets were easy to replace.

The best way I came up with is, upon windowDidLoad, traverse the view controller hierarchy and stop when a good match is found. Since even complex views won’t have hierarchies hundreds of levels deep, this doesn’t even take a noticeable amount of time.

This helper function looks for a match by type in a given view controller’s child collection, recursively:

func firstChildViewController<T: NSViewController>(
    _ parentViewController: NSViewController
    ) -> T? 
{
    for viewController in parentViewController.childViewControllers {
        if let match = viewController as? T { return match }
        if let subChild: T = firstChildViewController(viewController) { return subChild }
    }

    return nil
}

Since it’s generic, you just have to specify the desired type to look for so the compiler can figure out what you want:

let bananaVC: BananaViewController 
    = firstChildViewController(jungleViewController)

The only limitation is that this will stop on the first match. If you use the same type of NSViewController subclasses in multiple places, you to check for an additional criterion.

Inside my window controller, I use this function as follows:

class AmazingWindowController: NSWindowController {
    var bananaViewController: BananaViewController!
    var appleViewController: AppleViewController!
    
    override func windowDidLoad() {
        loadChildViewControllers()
        // ...
    }
    
    fileprivate func loadChildViewControllers() {
        self.bananaViewController = getChildViewController()
        self.appleViewController = getChildViewController()
    }
    
    fileprivate func getChildViewController<T: NSViewController>() -> T! {
        let firstViewController = self.window!.contentViewController!
        return firstChildViewController(firstViewController)
    }
}

Okay, so getChildViewController sounds like we’re back in Java-land. But it’s the best name I could come up with; “load” doesn’t fit since the view controller already is loaded. “Fetch” may work, or “find”. Whatever suits you.

With this in place, I am able to instantiate the child view controller reference properties in a fashion that resembles @IBOutlets closest.

And this, in turn, paves the way to extract the façade functionality from the window controller into another object. Nice!

NSSplitViewItem Vibrancy Is Not Added with Every Initializer

After yesterday’s wrap up of my fix for vibrant table views, I have discovered that NSSplitViewItem has 3 initializer variants since macOS 10.11:

  • init(contentListWithViewController: NSViewController)
  • init(sidebarWithViewController: NSViewController)
  • init(viewController: NSViewController)

The latter will not add vibrancy. Guess who, by accident, discovered his table view to be wrapped in a init(sidebarWithViewController:) call.

I’ll leave the other post up as reference for deactivating vibrancy and dealing with NSTableView text drawing intricacies.

Fixing NSTableView Cell Backgrounds by Deactivating NSSplitViewItem's Visual Effects

I discovered my own idiocy and found that I overlooked which initializer of NSSplitViewItem was called. Apparently, the sidebar-related one adds vibrancy by default. Dealing with un-vibrancifying a table view might still be interesting, so I leave this up.

Don’t jump to conclusions, folks, and don’t copy faulty code over to test projects: if I had typed the NSSplitViewItems setup code instead of pasting it in, I would’ve caught the initializer’s parameter difference.


My table view items looked odd for a while and I couldn’t figure out why. Until I disabled “Reduce transparency” in my System Preferences’s “Accessiblity” pane.

The various kinds of weird background artifacts that turned out to be behind-window blendings.

This was 0% my own genius and 100% thanks to a beta tester. (Thanks, Michel!)

I couldn’t reproduce this in a simple test app, no matter the settings of the views. Until I looked at the implementation of the split view controller. In the real app, I manage the split view with a NSSplitViewController subclass. Using this controller class, all of a sudden NSSplitViewItems are being wrapped in _NSSplitViewItemViewWrapper, each containing a NSVisualEffectsView. The dividers become “vibrant”, too.

NSSplitViewController only wraps the main pane in a visual effects view.** If you have a single pane, that will be wrapped. If you have 2 or more, only the pane at index #1 will be wrapped. Sidebars to the left and right are not affected, no matter how many you have. This assumed #1 is not a sidebar, of course.

Here’s the list of subviews of the NSSplitView when managed by a NSSplitViewController:

▿ 7 elements
  - 0 : <_NSSplitViewItemViewWrapper: 0x6000001a46e0>
  - 1 : <_NSSplitViewItemViewWrapper: 0x6080001a32c0>
  - 2 : <_NSSplitViewItemViewWrapper: 0x6080001a3480>
  - 3 : <NSVibrantSplitDividerView: 0x6080001863f0>
  - 4 : <NSVibrantSplitDividerView: 0x6080001864c0>
  - 5 : <_NSSplitViewSpringLoadingView: 0x608000169540>
  - 6 : <_NSSplitViewSpringLoadingView: 0x608000169600>

Debugging the UI shows this in the view hierarchy breadcrumb list:

Actual and expected view hierarchy

When you don’t use NSSplitViewController and its insertSplitViewItem(_:at:) or addSplitViewItem(_:), the visual effect views won’t be added. As expected. There’s no documented opting-out of this, either.

The first step in supporting vibrancy in a view is to contain the view in an instance of NSVisualEffectView. It is typically best to add vibrancy only to leaf views, not container views, because it is difficult to turn vibrancy off once it is on.
—Apple Docs for NSVisualEffectsView

Gee, thanks for adding vibrancy to the container view, then!

Deactivating Vibrancy

Either you don’t use NSSplitViewController or you deactivate vibrancy in the split view again. I imagine this is wasting CPU cycles, though.

To deactivate, you have to use your own NSVisualEffectsView and set its state property to NSVisualEffectState.inactive. This also works from within interface builder:

  1. Embed the split view item’s contents in a NSVisualEffectsView;
  2. Set or leave “Material” as “Match Appearance” (that is, don’t change anything from the next effects view in the hierarchy);
  3. Set “Blending Mode” to “Within Window” – or else the artifacts will stat.
  4. Set “State” to “Inactive”;
  5. Set the view’s appearance from “Vibrant Dark” to “Aqua”. That’s at the bottom of the attributes inspector where nothing interesting happens most of the time. If you miss this step, the table view cells will adjust the cell color to match the supposedly dark background and use a white text color automatically.

Optional: Fixing Editable Cells not Drawing White Background

Maybe it is just me, but figuring out custom table view backgrounds and adjusting the table cells accordingly is a rather painful experience. Even when the table looks right, editing the table cell contents now didn’t display opaque backgrounds.

If you enable “Draw Background” on the table cell’s NSTextField, you always get a white background.

You have to provide a background color before the window’s field editor appears. (Or you have to provide a custom field editor from your NSWindowController, but that will make your window drawing super slow.)

My favorite solution so far: program editing a cell myself.

The NSTableView behavior is set to “none”. I handle double-clicks and the Enter key myself and call cellView.beginEditing() on the selection. The cell view is configured thus:

class ResultTableCellView: NSTableCellView, NSTextFieldDelegate {

    func beginEditing() {

        guard let textField = self.textField else { return }

        textField.isEditable = true
        textField.isSelectable = true

        textField.selectText(nil)

        if let fieldEditor = textField.currentEditor() {

            fieldEditor.drawsBackground = true
            fieldEditor.backgroundColor = NSColor.white
            fieldEditor.textColor = NSColor.black
        }
    }

    private func endEditing() {

        guard let textField = self.textField else { return }

        textField.isEditable = false
        textField.backgroundColor = NSColor.clear
        textField.isSelectable = false

        textField.needsDisplay = true
    }

    override func controlTextDidEndEditing(_ obj: Notification) {
        
        endEditing()
    }
}

But that wasn’t quite enough. The field editor appearance was still mangled by default. So I had to stop the NSTextFieldCell from changing the field editor’s configuration:

class ResultTableTextFieldCell: VerticallyCenteredTextFieldCell {
    override func setUpFieldEditorAttributes(_ textObj: NSText) -> NSText {
        // Doing nothing is necessary to keep the editor white.
        return textObj
    }
}

And then, finally!, the text field obtains a white background when editing. When the user is not editing, the underlying NSTableRowView’s background can shine through.

Browse the archive

Subscribe via RSS