Latest Posts

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.

Adding last(where:) in Swift

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:

extension BidirectionalCollection
where Self.Indices.Iterator.Element == Self.Index {

    func last(
        where predicate: (Self.Iterator.Element) throws -> Bool
        ) rethrows -> Self.Iterator.Element? {

        for index in self.indices.reversed() {
            let element = self[index]
            if try predicate(element) {
                return element
            }
        }

        return nil
    }
}

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’”.

The 3 RxSwift Building Blocks of UI Components

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.

That doesn’t mean you couldn’t, thanks to KVO. Here’s a working but conceptually bad example of how to expose a view property in a reactive way that I wrote:

public extension Reactive where Base: NSScrollView {
    public var backgroundColor: ControlProperty<NSColor> {
        let source = 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`
        let observer = UIBindingObserver(UIElement: base) { (scrollView, newColor: NSColor) in
            scrollView.backgroundColor = newColor
        }

        return ControlProperty(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:

public extension Reactive where Base: BananaView {
    public var size: UIBindingObserver<BananaView, Size> {
        // `base` is a property of `Reactive` and of type `BananaView`
        return UIBindingObserver(UIElement: base) { (bananaView, newSize) in
            bananaView.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.

WebcamSnap Open Source Library Released

The picture cropping sheet in action

Another month goes by, another little macOS component was released.

This time, I wanted to have a very simple drop-in view component that takes pictures with the iSight camera of MacBooks or any other connected USB camera device. And if the user wants to crop the picture, I wanted to add that as post-processing.

Thought this would be a common need, but Google wasn’t helping much. So I coded this thing myself.

Have a look: https://github.com/CleanCocoa/WebcamSnap

How Taking Pictures with AVFoundation Works

Before I got it working, all the AVFoundation code looked pretty scary. But it’s simple, really, when you think about the possibilities of audio and video capturing and processing.

These are the requirements to take a photo with a USB camera using AVFoundation:

  1. you need a AVCaptureSession that controls the lifetime of the audio and/or video input and/or output;
  2. you specify output ports of type AVCaptureOutput, like the AVCaptureStillImageOutput I use here to grab a single image (instead of video or audio);
  3. you specify input ports of type AVCaptureInput, like the AVCaptureDeviceInput that takes a AVCaptureDevice, which in this case is of type AVMediaTypeVideo;
  4. you add a Core Animation layer (CALayer) to a preview view using AVCaptureVideoPreviewLayer(session:) so users see what the camera is showing before they hit the “Take Picture” button.

If things work out well, you have a session that can read video data from your camera device and streams the video signal to the preview. You can shoot photos, also known as “request still images” from the video stream, using captureStillImageAsynchronously on your AVCaptureStillImageOutput.

All of this setup is encapsulated in the Webcam object of my library. (links to the version of v1.0.0, no the latest, to prevent dead links in the future).

Hope it helps!

Do You Need to Use Action Creators in ReSwift to Conditional Action Dispatching? (No)

This is an answer to a StackOverflow question titled “When, why and how to use Action Creators in redux?” that is really targeted at ReSwift. The real question is phrased a bit differently, though:

For example, I have a button, after user pressed it I want to start some process if I’m in state A. So, I have to write an action creator, which will check current state and then return correct action, or not action at all. Then dispatch this action from the same place.

Redux.js and ReSwift behave quite differently. Redux recommendations may not apply in all cases.

Clearing up Redux.js/ReSwift confusions

Action Creators in ReSwift

“Action creators” is a rather specialized term in Redux.js, but the type is defined like this in ReSwift as of v4:

public typealias ActionCreator = (_ state: State, _ store: Store) -> Action?

So contrary to general advice, you do have access to the state at the point of dispatching the action. You don’t need a Thunk implementation, although Thunks or Epics can help in more complex cases.

“Action Creators help encapsulate action creation details”

A common benefit of Action Creators in Redux is their function as a factory.

Redux.js actions are object literals. Redux actions, if you were to write them in Swift, would be more like Dictionarys. If you come from an Objective-C background, you know both the good and bad of dictionaries: they are flexible, but you can cause trouble with typos, and the compiler won’t catch changes to values or keys if they are stringified. That’s how Action Creators in Redux.js provide the convenience of factories (as in “Factory”, the Gang of Four design pattern).

Here’s one as an example:

function addTodo(text) {
    return {
        type : "ADD_TODO",
        text
    }
}

// Somewhere else
dispatch(addTodo(text))

The ReSwift.Action type is usually implemented in custom value types (structs), though. ReSwift does not suffer from that problems of Redux.js actions. That means by creating a custom action type in ReSwift, the benefit of centralizing action creation in one function goes away. The initializer of your type does provide this already.

That paints a totally different picture.

And that’s probably why ReSwift.Store.ActionCreator passes in the state: to provide any benefit at all. At the cost of a different kind of API than Redux.

Application to the Question

Recall the question:

For example, I have a button, after user pressed it I want to start some process if I’m in state A. So, I have to write an action creator, which will check current state and then return correct action, or not action at all. Then dispatch this action from the same place.

There are a couple of ways to achieve that.

If you have access to the store variable to call dispatch, you also have access to its current state property. You can ask the store for the state the app is in and act accordingly. Usually, you’d write store subscribers to get “push notifications” of store changes, but in cases like this you can also ask the store.

That means the following would be a totally valid implementation:

let currentState = store.state
let action: Action = {
    if currentState.someSubstate == "A" {
        return ActionWhenInStateA()
    } else {
        return ActionWhenNotInStateA()
    }
}
store.dispatch(action)

Since ReSwift Stores are not supposed to receive dispatch commands from different threads, you can rely on the state from line 1 to be the same in the last line, where you dispatch.

TL;DR: you do not need ActionCreator to achieve this. But you can if you like to write “east-oriented” code, leaning on callbacks instead of property queries:

store.dispatch { state, _ in
    if state.someSubstate == "A" {
        return ActionWhenInStateA()
    } else {
        return ActionWhenNotInStateA()
    }
}

Browse the archive

Subscribe via RSS