inessential by Brent Simmons

March 2019

Follow-Up on Publishing NetNewsWire on the Mac App Store or Not

I got a lot of feedback on this question, and here’s where I ended up: don’t publish 5.0 on the Mac App Store — publish direct-only — and wait and see if the app is having the hoped-for reach, and then re-evaluate. (Here is perhaps the first tweet I saw along these lines.)

I like that, because it lets me avoid spending time on work I don’t want to do, and I can spend more time on features.

And, as some commenters reminded me, good old-fashioned marketing does more to bring users than just putting the app up on the app store.

(Note: there was always going to be a direct-download version. The question was whether or not to also publish on the Mac App Store. I should have made this clear in my initial post.)

The feedback was interesting, though. Very few people were adamantly for or against the Mac App Store — people who expressed an opinion tend to lean in one direction or the other, but it wasn’t a deal-breaker either way.

People who were for the Mac App Store cited security, having a single place for updates, and not having to deal with licenses. The licenses thing is not an issue for NetNewsWire, since it’s free, but I understand the other concerns.

People who were against the Mac App Store tended to note that, well, it’s a kind of silo. And more than one person suggested that an RSS reader — an app that’s all about getting people out of silos (Twitter, Facebook, etc.) — shouldn’t itself be in a silo.

(Of course, you might also say that you have to go where the people are in order to lead them out!)

* * *

Anyway, to reiterate — I’ll be doing a direct-download version and not publishing to the Mac App Store with 5.0.

But if I find, at some future date, that I believe it could reach substantially more people, and I believe the Mac App Store will help, then I’ll publish it there too.

NetNewsWire on Mac App Store or not?

I’m not ready to upload NetNewsWire to the Mac App Store — months away, probably — but it’s worth thinking about now.

I’m on the razor’s edge with this one. Here’s my thinking:

Reasons not to appear on the Mac App Store

I have nights and weekends to work on the app, and doing a sandboxed version and fulfilling all the metadata requirements is quite a bit of work. It’s probably at least a week, possibly two. (Again, because I don’t have all day every day to work on it.)

I’d rather spend that time working on the next version (and on my other app ideas). I have a lot to do!

And then there’s the issue of reviews — which for free apps (which this would be) are notoriously severe.

I can already picture them: “The developer ruined the one great thing about the app when he removed the in-app browser,” “This is a con game because it says syncing is a feature and it’s free but you still have to pay some other service for syncing, and I bet he gets a kick-back,” and “This app isn’t very Mac-like, plus [other app] is prettier.”

Do I need to read this kind of thing for an app I do for fun and give away (even the source code)?

And — last thing — the last time I uploaded NetNewsWire to the Mac App Store was NetNewsWire Lite 4.0 (in 2011, I think), and it was delayed several weeks due to an Apple issue (regarding WebKit and favicons). Do I want to deal with possible frustrations like that? Maybe that’s hardly a thing these days?

Reasons to appear on the Mac App Store

My political goal is to promote the open web — the web of indie publications and blogs, the web outside the silos. And that means getting as many people as possible using it.

Being on the Mac App Store will increase that number. It furthers my goal.

Which do I choose?

This is such a difficult case because I have no real idea how many more users the app would get by being on the app store.

And I don’t really know how much time it will take to do the extra work, and I don’t know that the reviews would be as bad as they often are for free apps.

Is the gain — the increase in users — worth it? Maybe?

Seattle Xcoders meeting this week is unofficial — no actual meeting. Just show up at the Cyclops (in the back, in the restaurant section) for food and/or drinks and conversation. Thursday (March 28) at 6:30 pm.

Lots of Apple news this week! But of course we talk about whatever. We could talk about Apple stuff if you want to. :)

NetNewsWire Sync Diary #1: It Starts with NSBezierPath

In my Vesper Sync Diary I wrote about designing and writing both sides of a syncing system. I could make syncing work however I wanted it to work, and I had control of all of it.

Which is a great way to go. But it’s also terrible, because it means writing and running a server.

With NetNewsWire, I’m lucky in that there are existing systems — such as Feedbin, Feedly, Newsblur, and others — to use. I only have to write the client side.

I’m starting with Feedbin as the first syncing system. The first one is the hardest, of course — since there are a bunch of things the app needs for syncing that can be written once, but that do still need to be written.

How syncing will work

Users will add accounts to NetNewsWire — much as you do in Mail. Each account will have its own section in the sidebar (again, like Mail), which means you could have an On My Mac and a Feedbin account both active at the same time. (Just like you might have work and home email accounts active at the same time in Mail.)

You could even have multiple On My Mac and multiple Feedbin accounts. And, later, accounts with other systems.

When you make a change to the structure of your feeds and folders, the app will communicate immediately with the syncing system to perform the appropriate action. When you make a change to the metadata of an article (such as read/unread/starred status), the action will be coalesced and periodically (though fairly quickly) sent with other actions to the syncing system.

NSBezierPath

So I got started working on this, and almost immediately I found myself doing some custom drawing using NSBezierPath (which is a thing you use to fill and stroke lines, rectangles, circles, and other shapes).

Which sounds crazy, right? NetNewsWire has almost no custom drawing — in fact, before this, the only custom drawing was for the unread indicator (a circle).

And — bigger point — what does drawing have to do with syncing, anyway?

Well. There’s UI. Of course there’s UI — there’s always UI.

Users need to be able to add and edit their sync accounts, and I decided to do it the same way (surprise!) as in Mail — because this way the UI will be familiar to people who use Mail, which is surely a lot of Mac users.

I added a preferences pane called Accounts, and it uses the system-supplied icon for this exact case, and it has a thing like this on the left side…

Screenshot of the table and buttons on left side of the Account preferences

…or…

Screenshot, in dark mode, of the table and buttons on left side of the Account preferences

Looks much like Mail, right? (Not exactly the same, but it’s not important to make it exactly the same.)

The big space on top is a standard NSTableView, and the two buttons underneath are standard gradient buttons.

But what about that bordered gray thing to the right of the buttons? It has a background color and a border on top, right, and bottom edges — but not on the left edge, because that border belongs to the button.

There are a bunch of different ways I could have composed this whole thing, but in the end I just made that a custom view — AccountsControlsBackgroundView.swift — that draws its background and draws those three lines as a border.

(There’s probably an even simpler way using just one NSBezierPath instead of three. Or even a few NSBoxes used as lines. Whatever.)

My point isn’t that there’s anything interesting about this drawing — it’s that, when you go to work on a feature like syncing, the actual meat of the feature (communicating with sync servers) is often very small and easy.

User interface — design and implementation — tends to take up the bulk of the time, by far. By an amount that would surely surprise the heck out of people who don’t make apps.

In other words, it should never be surprising that work on a feature like syncing starts with an NSBezierPath.

Next step

What you see in the screenshots above is as far as I’ve gotten so far. The table view is empty; the buttons aren’t hooked up to anything.

The next step is to make that table view show a list of accounts, which, at the moment, is just the default On My Mac account. It should show the name and type of the account, along with an icon which represents the fact that it’s local-only and not synced.

We don’t take him seriously because he’s crazy.

But crazy people do take him seriously.

Big story on RSS readers in the iOS App Store today:

Really simple syndication — better known as RSS — is an enduringly useful technology that collects online articles from various sources in one convenient place.

The bummer about these articles is that the full thing can only be read in the iOS App Store. It would be nice if they actually appeared on the web, too.

The articles are often very well done and beautifully illustrated — and it would be to the benefit of Apple, and app developers, if these articles were findable and readable by people sitting in front of a computer.

Some More RSS-y Things

I didn’t know that NewsBlur was open source — including the iOS app — and that David Sinclair works on it. Very cool.

* * *

The iDownloadBlog ends an article on Reeder 4 beta with this:

Is there anyone out there still using RSS?

Don’t tell me I’m the only one…

I get it. And I realize the person was kind of joking around.

But here’s the thing: tons of people use RSS readers. There’s no shame in it; you’re not the last person; there’s not going to be a last person.

This category of software is not going away, and no one needs to be meek about it. There are probably more RSS reader apps these days than ever before.

Other services for getting news and links exist too. But there are lots and lots of people who use RSS readers. Not still using them, as if they’re stuck in the mud. No: they are people who like them and see no reason to stop liking them! (Many of those people use other services too. Of course!)

* * *

Nick Heer writes about developments on the RSS front:

I think the hardest question for RSS readers is how it could gain broader interest outside of the more technically sophisticated user group.

I agree that that’s a hard question, but I’m also not sure if it’s the most important challenge. I’m not writing an RSS reader because I expect a mass market to use it. I think lots of people will, but nothing like the number of Mac users who use Twitter, Facebook, or even Apple News.

That said… we should be trying to bring the benefits of RSS readers to more people based on ethical grounds. Deliberately — or through inaction — reserving technology for a sophisticated group is Not a Good Thing.

The answer is probably a collection of answers, and the way to get there is step by step.

Reeder 4 beta for Mac is up!

I had been on the verge of writing about this. I’d noticed some people saying things to the effect of, “Oh, he must have stopped working on it. He’s been so quiet.”

This is one of those things that drives me nuts. When a developer says they’re working on something, and then they’re quiet, it’s because they’re busy working on it.

Back in the old days, when I would hear that someone was writing an RSS reader, I’d worry that it was more competition.

These days when I hear that same news, I get excited. Sure, I want lots of people to use NetNewsWire — but, way more than that, I want lots of people to use RSS readers.

The more the better, since no one app is going to be the right thing for everybody.

And here’s Rob Fahrni working on a river-of-news type RSS reader for Mac and iOS. Cool!

Writing an open source app, and not having to think about making money with it, is utterly thrilling. I love this.

Among other things, it means I get to be a cheerleader for other people’s work, which is totally fun for me. Makes me happy.

Feed Discovery with Feed Compass

Maurice Parker, who’s contributed a lot to NetNewsWire, has started working on a directory app for feeds: Feed Compass.

It’s still at the proof-of-concept phase — very early days. The idea is that it can read OPML files from the web, show you a list of feeds, and show a preview of what’s in a feed when you select one. Most importantly, there’s a big Subscribe button so you can add the feed to your RSS reader.

The subscribe button should work with any Mac RSS reader that supports the feed protocol (which is probably all of them). It’s not just NetNewsWire-specific, in other words — it should work with Reeder, ReadKit, and others. (I haven’t tested this, though.)

* * *

This isn’t all of what needs to be done to make feed discovery easier and better. It’s a step, though, and it can be combined with other steps.

Some things I like about it:

  • It’s decentralized. It doesn’t rely on any one server, since servers may come and go.
  • It’s open source, which means you can contribute and/or fork. The code can’t go away.
  • It’s free, which means there’s no barrier to use.
  • It works (as I mentioned) with any Mac RSS reader, not any one in particular.
  • It could be ported to iOS.
  • It means we don’t need to include a feed directory built-in to NetNewsWire. (This helps keep NetNewsWire focused.)

* * *

Again, this is just the start of something. It’s not the one and only true answer to the problem of feed discovery — but it could be part of a collection of solutions that work together.

For more about discovery… When Maurice was doing research on the topic, he found this thread on Dave Winer’s GitHub enlightening, and I recommend reading it.

* * *

I had posted, to the NetNewsWire Slack group (email me for an invitation), a list of RSS app ideas. These are apps that could use NetNewsWire code to get a head start (though you wouldn’t have to).

The below is what I posted…

Here are some ideas for apps I’d love to write, that could use some NetNewsWire code, but that I’m too busy to write — but that would be cool if someone else wrote!

Big Internet Backup — discussed earlier. Give it a bunch of feeds, and it keeps everything forver. Searchable. Smart feeds. Etc.

Timeline/river-of-news reader - RSS presented as in a Twitter client. Reverse-chronological list. Super simple. Probably doesn’t even track read/unread (just position in timeline).

Microblog client as productivity app — multiple accounts, standard sidebar. Features like drafts. Scriptable. Searchable. Keeps items for a long time. Possibly even a detail pane that shows linked-to web pages. Connection to MarsEdit.

Feed creator — makes it easy to create feeds, including podcast and appcast feeds. (For instance: I do the appcast feed for NetNewsWire by hand, which is crazy. There should be an app for this.)

Feed debugger - given a feed, this app tells you about the issues. Is it a valid feed? Does it support conditional GET? Are attributes missing? Misspelled? Etc. This is especially important as it appears that feedvalidator.org may be down for the count.

Feed directory - reads in OPML from various sources on the web (such as Dave Verwer’s and Dave Winer’s collections) and creates a directory. When you select a feed, it downloads it and shows a preview of what you’d get. One-click subscribe to the default reader (whether it’s NetNewsWire or another app).

Though ideally these would be open source apps, they don’t have to be. (You can use NetNewsWire code in commercial apps, as long as you credit NetNewsWire in the about box or somewhere appropriate.)

I don’t claim that any of these are money-makers. Maybe some of them? Depends. I do think they’d all be very useful, and that you have a leg up since you can start with NetNewsWire code.

Questions about Marzipan Apps on the Mac

I’m thinking in advance about the things I’d like to know about Marzipan.

My questions, in no particular order:

  • Will we be able to ship Marzipan apps that run only on the Mac?
  • Will we be able to ship Marzipan Mac apps outside of the App Store?
  • Will there be a universal app format that includes iOS and Mac versions?
  • Will Marzipan Mac apps appear in the Mac App Store?
  • Will AppKit be deprecated?
  • If AppKit is not deprecated formally, will it get much in the way of new features?
  • Will apps have access to AppKit? Can they mix-and-match UIKit and AppKit?
  • Will apps be able to support AppleScript?
  • Will apps be able to send Apple events to other apps?
  • Will we be able to edit the main menu in Interface Builder?
  • Will apps support standard Mac customizable toolbars? If so, can these be edited in Interface Builder?
  • Will apps have resizable splitviews (without having to write this ourselves)?
  • Will apps support fullscreen with collapsible/slide-out-y sidebars?
  • Will apps support three-paned splitviews (a la Mail) without having to write the whole thing yourself?
  • Will document-based apps be able to open a separate window per document?
  • Will apps support additional windows? (In Apple News, choose File > Discover Channels & Topics… — what you really want is for that to come up in a separate window, I would think.)
  • Will apps support floating windows (such as for an inspector or a social media compose-message window)?
  • Will the Macintosh Human Interface Guidelines be updated to account for the differences in Marzipan apps?
  • Will table views support drag-and-drop to reorganize without doing the iOS modal thing with the grabbers in the cells?
  • Will there be some kind of way to use NSOutlineView (or equivalent)?
  • Will table views support standard Mac highlighting? And multiple selection?
  • Will sandboxing be required for Mac Marzipan apps?
  • Will the tab key work to move focus from view to view?
  • Will apps be able to use Mac-only frameworks such as SearchKit?
  • Is Apple working on a cross-platform, pure Swift app framework that would make Marzipan, UIKit, and AppKit obsolete?

We have hints at some of the answers — see Steven Troughton-Smith’s blog. (Subscribe to his feed.) He’s doing excellent work.

But it’s worth remembering that we don’t really know much yet, because even what we’ve learned so far may not reflect the actual shipping reality.

* * *

I care about the answers as a developer. Though I’m not going to rewrite NetNewsWire as a Marzipan app, I do have a bunch of other app ideas I’d like to do, and many of those should be iOS apps as well as Mac apps.

If Marzipan means I can get those apps made more easily and in less time — and that the Mac versions will be as good as an AppKit app, without compromises — then I’ll be happy to adopt it.

But that part is critical. It has to be as good a Mac app as the AppKit version that I would have written. Otherwise it’s not worth my time. Other people may make other calculations, and I respect that.

(Note to those who think of me as a Mac-only programmer: I’ve written several iOS apps, including Vesper, Glassboard, and early versions of NetNewsWire, AllThingsD, and Variety.)

Implementing Single-Key Shortcuts in NetNewsWire

NetNewsWire supports using single-key shortcuts for some commands: the k key marks all as read, for instance. The space bar scrolls the current article, or goes to the next unread if there’s nothing more to scroll.

There are a bunch of these, and they’re documented: see NetNewsWire Help menu > Keyboard Shortcuts.

These kinds of shortcuts are fairly rare in Mac apps, and rightly so — they’re best for apps where power users might need to process a bunch of stuff and move around quickly. That doesn’t describe every app (thank goodness!).

Since this isn’t really a standard AppKit thing, you might wonder how I implemented this in NetNewsWire.

Getting keyboard events

You have to override keyDown(with event: NSEvent) — so NetNewsWire has subclasses such as SidebarOutlineView in order to get keyboard events.

I could, if I wanted, just get the character from the event and do a switch statement with code like this:

case 'k':
	markAllAsRead()

This gets ugly pretty quickly, and, importantly, I plan to make keyboard shortcuts customizable (at some point), so I chose a different path.

Property List Files

If they’re customizable, then the specifiers need to be stored in some data format. Property lists (plists) are a good choice, since they’re easy to read and edit in Xcode.

Now, I’m not making them customizable yet, but I figured it would be smart to use the same format and implementation for the default sets of shortcuts.

Inside the plists

There are a few plist files: one for global shortcuts, one for the sidebar, one for the timeline, etc. (I could have made just one file, but I didn’t. Doesn’t matter.)

Each file contains an array of shortcuts; each shortcut has a key and an action. The key is the actual key, such as k or ' — or a placeholder such as [rightarrow] where the actual key couldn’t be listed in the plist. (The app translates placeholders into their actual values.)

A shortcut may also have one or more modifiers, and those are represented as booleans: shiftModifier, for instance. This way we can differentiate between the space key and shift-space.

The action part is a string specifying which method to call for the given shortcut.

Here’s an example of one of those files.

The keyDown implementation

SidebarOutlineView overrides keydown:

override func keyDown(with event: NSEvent) {
	if keyboardDelegate.keydown(event, in: self) {
		return
	}
	super.keyDown(with: event)
}

The keyboardDelegate gets the first shot at handling the key. If it doesn’t handle it, then normal processing continues.

SidebarKeyboardDelegate

There are several keyboard delegates, but let’s look at SidebarKeyboardDelegate, since these all work about the same.

In init it reads its keyboard shortcut definitions from a plist in the app bundle and creates a Set<KeyboardShortcut>. A KeyboardShortcut is a KeyboardKey (defined in same file) plus an action string.

A KeyboardKey describes the key, including its integer value and any combination of modifiers (shift, option, command, control).

In keyDown, SidebarKeyboardDelegate first gives MainWindowKeyboardHandler a chance to handle the key. MainWindowKeyboardHandler handles keys that are global across views.

If MainWindowKeyboardHandler doesn’t handle it, then it looks for a KeyboardShortcut that matches the pressed key.

let key = KeyboardKey(with: event)
guard let matchingShortcut = KeyboardShortcut.findMatchingShortcut(in: shortcuts, key: key) else {
	return false
}

If it finds a matching shortcut, then it makes the shortcut do the thing it’s supposed to do:

matchingShortcut.perform(with: view)

Performing the shortcut

Remember that a KeyboardShortcut has a KeyboardKey and an actionString. That string is pulled from the plist: it’s something like nextUnread: or openInBrowser:.

We turn this into a selector using NSSelectorFromString and store it in a local variable action.

(The Objective-C runtime has a particularly beautiful thing: messages are real things, and they exist separately from objects.)

The perform(with:) method looks like this:

public func perform(with view: NSView) {
	let action = NSSelectorFromString(actionString)
	NSApplication.shared.sendAction(action, to: nil, from: view)
}

Here’s Apple’s documentation on NSApplication.shared.sendAction.

The short version of what it does: if to (the target) is nil, it starts with the first responder, then checks its nextResponder, then its nextResponder, and so on until it finds an object that responds to the specified selector (the action) — and then it asks that object to perform that method (or: it sends that message to the receiver). (It also checks the window’s delegate and some other things before giving up, when it gets that far.)

This means, for instance, that I don’t have to implement openInBrowser: in SidebarOutlineView. It could be implemented in its view controller, in MainWindowController, etc., as long as there is an implementation in the responder chain.

This way the implementation can be placed where it makes sense. I can even move openInBrowser: without needing to change the plist configuration.

And — this is important — it means that there might be (and there are, in NetNewsWire) multiple implementations of openInBrowser:. The sidebar has one which opens the home page of the selected feed. The timeline has a different one which opens the URL for the selected article.

The one that gets called is based on which one of these is the first responder, because that’s where the responder chain starts. (Which is another way of saying: the implementation that gets called is based on what thing has user focus.)

Both of these openInBrowser: commands are represented by the same KeyboardShortcut from the global keyboard shortcuts plist.

Not Magic

It may seem like NSApplication.shared.sendAction is doing something reserved for Apple that you couldn’t do, or couldn’t do easily.

But you could actually write this yourself. This simple version (which just checks nextResponder) has all the critical bits.

func sendAction(_ action: Selector, to target: Any?, from sender: Any?) -> Bool {
	var responder = NSApplication.shared.keyWindow?.firstResponder
	while responder != nil {
		if responder?.responds(to: action) ?? false {
			responder?.perform(action, with: sender)
			return true
		}
		responder = responder?.nextResponder
	}
	return false
}

Obviously we don’t have the source to NSApplication.shared.sendAction — but, at least in concept, that’s all it’s doing. (Minus the part where it checks some things after exhausting nextResponder.)

Summary

In a Mac app, how does the Edit menu‘s Copy command know what to do? It walks the responder chain: the first to claim that it responds to the copy: message then performs the copy: method.

You don’t have just one copy: method somewhere that has to figure out the context (figure out what has focus) and then do the right thing. Instead, you may have multiple copy: methods.

(This is much less of a thing on iOS. But when you go to use Marzipan with your iOS apps, there’s a good chance you’re going to need to know about this.)

The Copy command (and others) use the same pattern I’m using here, in other words.

It’s also worth thinking about how nibs and storyboards actually get loaded by your app. All the wired-up actions are, at some level, just strings — so the frameworks (UIKit and AppKit) use NSSelectorFromString to turn these strings into real messages.

Having an idea of how all this works under-the-hood is useful knowledge: it means you understand your app better. It also means that when you’re faced with something like implementing a keyboard shortcuts system, you’ll have the right tools for the job.

(Of course this isn’t the right tool for every job.)

PS Try this!

In Xcode, set a symbolic breakpoint for NSSelectorFromString. Make the Action a sound. Check the box next to “Automatically continue after evaluating actions” — this way it won’t actually stop.

Now launch your app. Even if you don’t ever use this, your app sure does!

And, for bonus points, also set a similar breakpoint for NSClassFromString. Give it a different sound.