Latest Posts

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:

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?

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:

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:

classViewController:NSViewController,NSLayoutManagerDelegate{letlineHeightMultiple:CGFloat=1.6letfont:NSFont=NSFont.systemFont(ofSize:NSFont.systemFontDefaultSize())publicfunclayoutManager(_layoutManager:NSLayoutManager,shouldSetLineFragmentRectlineFragmentRect:UnsafeMutablePointer<NSRect>,lineFragmentUsedRect:UnsafeMutablePointer<NSRect>,baselineOffset:UnsafeMutablePointer<CGFloat>,intextContainer:NSTextContainer,forGlyphRangeglyphRange:NSRange)->Bool{letfontLineHeight=layoutManager.defaultLineHeight(for:font)letlineHeight=fontLineHeight*lineHeightMultipleletbaselineNudge=(lineHeight-fontLineHeight)// The following factor is a result of experimentation:*0.6varrect=lineFragmentRect.pointeerect.size.height=lineHeightvarusedRect=lineFragmentUsedRect.pointeeusedRect.size.height=max(lineHeight,usedRect.size.height)// keep emoji sizeslineFragmentRect.pointee=rectlineFragmentUsedRect.pointee=usedRectbaselineOffset.pointee=baselineOffset.pointee+baselineNudgereturntrue}}

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:

NSLayoutManager.setExtraLineFragmentRect(_:,usedRect:,textContainer:) itself can be changed to call super with a different line height.

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.

classLayoutManager:NSLayoutManager{varlineHeightMultiple:CGFloat=1.6privatevarfont:NSFont{returnself.firstTextView?.font??NSFont.systemFont(ofSize:NSFont.systemFontSize())}privatevarlineHeight:CGFloat{letfontLineHeight=self.defaultLineHeight(for:font)letlineHeight=fontLineHeight*lineHeightMultiplereturnlineHeight}// Takes care only of the last empty newline in the text backing// store, or totally empty text views.overridefuncsetExtraLineFragmentRect(_fragmentRect:NSRect,usedRect:NSRect,textContainercontainer:NSTextContainer){// This is only called when editing, and re-computing the // `lineHeight` isn't that expensive, so I do no caching.letlineHeight=self.lineHeightvarfragmentRect=fragmentRectfragmentRect.size.height=lineHeightvarusedRect=usedRectusedRect.size.height=lineHeightsuper.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 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:

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:

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:

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.

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:

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:

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!

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:

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:

Embed the split view item’s contents in a NSVisualEffectsView;

Set or leave “Material” as “Match Appearance” (that is, don’t change anything from the next effects view in the hierarchy);

Set “Blending Mode” to “Within Window” – or else the artifacts will stat.

Set “State” to “Inactive”;

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:

For quite a while I didn’t notice Sequence.first(where:) exists. It’s like first, only with a condition. Proposed and implemented by Russ Bishop, by the way. I have now happily migrated from my self-baked findFirst to this method – only to find out today that there’s not last(where:) equivalent.

Makes sense at first, since Sequence is not stride-able backwards. But BidirectionalCollection is, and thus Array.

In a huge collection of stuff, it’s probably too costly to simple call array.reversed().first(where: myPredicate). Here, not even lazy would help since reversed() returns an Array in the new order instead of a ReversedBidirectionalCollection<LazyCollection<Whatever>> or similar. So I’m cautious and prefer to enumerate backwards.

BidirectionalCollection has an indices property. In case of arrays, that’s a CountableRange<Int> which can be reversed far more cheaply. And the Indices associated type of BidirectionalCollection supports reversed(), too, so we can generalize to this:

This iterates backwards without reversing the whole collection at first. Works with arrays and other interesting types like Ole Begemann’s SortedArray.

By the way: why on earth would BidirectionalCollection.Indices.Iterator.Element be allowed to ever not equal BidirectionalCollection.Index? Without the “where” clause, the compiler will complain about the subscript: “Cannot subscript a value of type ‘Self’ with an index of type ‘Self.Indices.Iterator.Element’”.

So here’s what I learned so far about the building blocks of reactive UI components from peeking at the RxCocoa source.

The 3 Building Blocks

UI components in general can have properties (read/write), input ports (read), and output ports (write). Classic UIKit/AppKit output ports would be delegate calls; classic input ports would be commands like display(banana:) that you probably write every day or so.

Translated to the world of RxSwift:

Observable is the basic output sequence

Observer is the basic consumer of input event sequences

a combination of both fits mutable properties, the like Variable type, for example

Then there are special “traits” for UI bindings which guarantee to work on the main queue.

ControlEvent is an Observable trait

UIBindingObserver is an Observer trait

ControlProperty has both traits’s attributes and is an Observable sequence as well as an Observer

Translating Known UIControl/NSControl Properties to RxSwift

For example, NSTextField and UITextField expose .rx.text which is a ControlProperty<String>. That means it’s read-write. You can bind other sequences to this property and have the text field update its content; and you can observe user-generated changes as well.

The alpha value or translucency of a view component is a UIBindingObserver; similarly, isEnabled is a UIBindingObserver. Look at UIControl+Rx of RxCocoa for details.

The reasoning I came up with: you usually want to change the alpha value or enabled state in reaction to some event, so it needs to be an Observer (or “sink”) of sorts. You don’t expect any view component to generate changes to these on its own terms. These things are toggles that a controller usually manipulates.

So even though the underlying property of the UIKit/AppKit component is a readwrite (or var) property, it does not make too much sense to expose a ControlProperty in these cases.

publicextensionReactivewhereBase:NSScrollView{publicvarbackgroundColor:ControlProperty<NSColor>{letsource=self.observeWeakly(NSColor.self,"backgroundColor",options:[.initial,.new])// Skip nil values (which in practice does not happen, but KVO observe returns Optional).filter{$0!=nil}.map{$0!}.takeUntil(deallocated)// `base` is a property of `Reactive` and, here, of type `NSScrollView`letobserver=UIBindingObserver(UIElement:base){(scrollView,newColor:NSColor)inscrollView.backgroundColor=newColor}returnControlProperty(values:source,valueSink:observer)}}

As you see, the ControlProperty is composed of a UIBindingObserver and a regular Observable sequence. But there’s a catch: ControlProperty requires your source sequence to adhere to a few conventional criteria. These criteria are not enforced through the types in this case. ControlProperty takes responsibility of adhering to certain characteristics but it’s your job to make them happen. Let’s talk about these rules in detail.

The Rules of Using These 3 RxCocoa Types Correctly

ControlEvent

The implementer (you!) has to provide an Observable that’s safe and auto-completes on deallocation of the object. ControlEvent itself takes care of main queue scheduling if needed.

Quoting the Swift source of RxCocoa v3.5, ControlEvent …

it never fails

it won’t send any initial value on subscription

it will Complete sequence on control being deallocated

it never errors out

it delivers events on MainScheduler.instance

Most Observable sequences you build don’t send initial values, but there are some that have replay behavior you should be aware of.

For example, a Variable("initial") sends .next("initial") when subscription starts. A BehaviorSubject<String>("inital") does, too. But a PublishSubject<String>() doesn’t send anything upon subscription. You can see that it doesn’t require an initial value. Observable.from(["initial"]) will start right away, too.

ControlProperty

The requirements of ControlProperty are similar to these of ControlEvent, but an initial value is expected, so the source has to shareReplay(1).

Again, ControlProperty itself takes care of main queue scheduling.

it never fails

shareReplay(1) behavior

it’s stateful, upon subscription (calling subscribe) last element is immediately replayed if it was produced

it will Complete sequence on control being deallocated

it never errors out

it delivers events on MainScheduler.instance

UIBindingObserver

The “soft” attributes (that is, not enforced through the typing system) of UIBindingObserver are simpler: it does not bind errors (errors will merely be logged). And if the incoming sequence is not on the main queue already, UIBindingObserver dispatches to the main queue asynchronously.

From the call site, you don’t have to do anything.

It’s pretty easy to use:

publicextensionReactivewhereBase:BananaView{publicvarsize:UIBindingObserver<BananaView,Size>{// `base` is a property of `Reactive` and of type `BananaView`returnUIBindingObserver(UIElement:base){(bananaView,newSize)inbananaView.growVisibleBanana(to:newSize)}}}

You pass in an object to UIBindingObserver.init that you want to change for incoming events. UIBindingObserver does the weak–strong-dance for you. It keeps a weak reference to the element only, so you don’t end up with a retain cycle. When a .next event reaches the observer, and if the weakly referenced element still exists, a reference to the element is passed back into the closure where you can mutate the underlying property.

If you create your objects using Variable type properties, you don’t need much of this stuff, of course. But if you want to provide a reactive extension to a regular view component, this is how you can do.