Monthly Archives: July 2018

SwiftoDo Development Notes, July 2018

I took a break from SwiftoDo development to build a new app, Simple Call Blocker, which I posted about earlier this week. Building it was a fun diversion, and I learned a lot in the process, too. If you’re reading this, you’re probably wondering what’s up with SwiftoDo, however. I have been working on it this summer, too.

Since this spring, I have been promising everyone that I’m working on iOS 11+ Files app integration. This feature will let SwiftoDo open files from any cloud service provider that ties into the iOS 11+ Files app. That includes Dropbox, Box, NextCloud, OneDrive, Google Drive, and many others. Adding additional data providers to SwiftoDo has been a long time coming.

Some background on SwiftoDo data providers

I designed SwiftoDo data providers to be plugins that could be swapped out, which would allow me to support numerous cloud storage providers. I have had so much trouble getting the Dropbox data provider to be solid and stable, however, that I was loath to create more data providers. My thinking was that I was liable to cause more problems for my users (and myself!) than I would solve. Therefore, I concentrated on fixing up the Dropbox data provider code, which is now, after three or four rewrites, pretty solid.

(The Dropbox data provider code has always worked, but it had a rare but nasty crashing bug for some time, and some issues related to stability, ability to handle spotty network connections, and ability to handle, gracefully, Dropbox “rate limit exceeded” API responses.)

iOS 11’s Files app, which I did not anticipate being available when I first wrote SwiftoDo, obviates the need for additional “native” data providers. I just wasn’t sure if SwiftoDo would be able to tie into it.

Can Files integration be used for SwiftoDo?

I have been under the impression that integrating with the Files app would require a rewrite of major sections of the app, or would simply not work well due to sandboxing limitations. I thought this because all Files-based iOS apps that I have used follow Apple’s “Document-based app” template very closely: Think “Microsoft Word” rather than “Reminders” in terms of user interaction patterns. They open to a file browser, you open a document (typically a single document), work on the document, save it (automatically), and close the app. The life cycle of a document is fairly limited: after the app is killed, your file is closed and does not reopen on load. Most of these apps don’t work offline at all, unless you’re working with local-only files, because they can’t open your file.

In contrast, SwiftoDo works a lot differently. It manages your task list locally and syncs it to an external file. It lets you archive completed tasks to a second external file. It keeps the opening and choosing of files down to a minimum. Most users set up their todo.txt and archive files once and never touch those settings again. It lets you work offline (primarily in “manual sync mode”, but also when the network is unavailable) and sync your changes to the cloud on demand.

Integrating SwiftoDo with the Files app, without giving up anything, seemed like it might be a considerable challenge.

A “Files” data provider

To my surprise, implementing a “Files” data provider has not been as challenging as I thought it would be. Accessing documents via the iOS document picker and restoring them after the app is killed, via secure bookmarks, is actually pretty easy. It took me only a couple hours to set up a data provider that would upload and download to iCloud Drive and even Dropbox through the Files app.

That is not nearly enough to ship the feature, though. There are still some issues regarding stability and error handling that I have to work through.

Adding this feature also prompted me to display file names in Settings (rather than 2 screens deep in Settings) and atop the task list in the main view.

What is next?

After I finish the Files data provider work, and the new Xcode and iOS versions are officially released, I plan to build SwiftoDo on the iOS 12 SDK and drop iOS 10 support. I’ve been thinking of bumping the version number to 3.0 at that point, to mark the change in iOS compatibility.

I will also consider the future of Files integration in SwiftoDo. It temping for me to remove the entire “data provider” layer and just make SwiftoDo a normal iOS document-based app. That would be a big deal, and I would not make that change unless I understood fully what that would mean for users. I also have to consider how long I will continue to support the existing Dropbox data provider, as it will be somewhat redundant.

After that, I will have the opportunity to simplify the codebase quite a bit. It is tempting for me to rewrite some or all of the UI layer, to incorporate the new techniques I have learned since coding version 2, over a year ago. Any changes to the UI code will probably be related to new features or a minor redesign of the sorting/filtering interface that I have been thinking about.

Introducing: Simple Call Blocker

Recently I released a new iOS app: Simple Call Blocker.

It is a free utility that lets you block unwanted calls to your iPhone. Unlike most of the call blockers on iOS, it allows you to block whole ranges of numbers, such as your phone number’s extension, for free. You may also whitelist numbers, ranges of numbers, or all your contacts’ phone numbers, so that they will not be blocked by this app, even if they are in the blacklist.

The Simple Call Blocker website explains it in more detail.

Why did I write it?

I wanted to write a small, relatively simple app that would allow me to explore the following things:

  1. iOS application architecture: I have been reading books and articles on iOS application architecture, and decided to create a new app to practice new techniques, such as the use of coordinators for navigation rather than storyboard segues, that I have been learning.
  2. algorithms and operation queues: I started the app by writing some simple algorithms for creating sequential phone number ranges to load into an iOS CallKit directory extension. The last thing I wrote was a multi-threaded operation queue for processing blacklist and whitelist rules and refreshing the iOS call directory extension that actually blocks the phone numbers.
  3. Core Data: I avoided learning Core Data for years; that changed with this app.

Overall, the app was a lot of fun to write. It took me about a month in my after-hours “free time” to create. The overall process has made me a better iOS app developer. I’m excited to bring forward the skills and concepts I developed on this project to future work.

Why a call blocker app?

I started getting neighbor spam calls, so I downloaded nearly all the iOS call blocking apps I could find. There were fewer such apps than I thought there would be, all of them seemed amateurish in some way or another, and all of them (as of a month ago) required an in-app purchase or a subscription to block my neighborhood exchange. I wasn’t willing to pay for that feature in any of the apps that I tried, because all of those apps weren’t that good. Plus, blocking an exchange, or a continuous range of numbers, is pretty trivial, so I thought I could create an app that did that, and offer it for free to people.

So is this just a “practice” app?

No. It is well-written and works as well as iOS’s Call Directory extensions allow it to. It has a rough edge or two, though, in that it reloads its directory extension and reports success or errors back to the users, rather than trying to prevent the user from ever encountering an error from being reported by the directory extension loading process.

What that means is that you can ask Simple Call Blocker to block more numbers that iOS will allow—there is an undocumented limit—and the app has to wait for the CallKit Directory Extension’s load process to report success or failure before it can tell the user what is going on. I decided not to try to impose limits on how many numbers could be blacklisted, but instead allow the app to report the Directory Extension’s errors, if any, back to the user. I figure that the undocumented maximum number of blocked phone numbers probably is dependent on whatever hardware is in your phone, and probably is increasing in every new iPhone model.

The Simple Call Blocker directory extension is coded extremely conservatively, and it optimized for very low RAM and system usage. If iOS cannot load it, it is because the user put too many numbers in the blacklist, or because it is still loading numbers from a prior attempt.

Releasing the app for free, I think, makes it OK that it may not work exactly as users expect it to.

What is the future of this app?

I plan to support it through various iOS releases, but otherwise not improve it too much. After all, it is a free app.

I want to know more!

Go ahead and download the app in the iOS App Store (it’s free!), and check out the FAQ online.

On Internet Trolls

DON’T FEED THE TROLLS, AND OTHER HIDEOUS LIES” is a great article by “Film Crit Hulk” on our collective failure to respond properly to internet trolling culture.

A Twitter follower reminded me of a line in the famous parable from Bion of Borysthenes: “Boys throw stones at frogs in fun, but the frogs do not die in fun, but in earnest.” Defenders of trolling insist it’s all just a joke, but if trolling is inherently designed to get a rise out of someone, then that’s what it really is. In many cases, it is designed to look and feel indistinguishable from a genuine attack. Whether you believe what you are saying or not is often immaterial because the impact is the same — and you are responsible for it, regardless of how funny you think it is.

I think that there is a fundamental misunderstanding of trolling. It isn’t a joke. It isn’t done for the lulz. “It’s just a joke” is an obvious cover for bad behavior.

It reminds me of an episode from my youth. In high school I had a friend who had a stash of Playboy magazines that he got (I think) from an older brother. Somehow we found out about them, demanded to see them, and teased him about them as we thumbed through them together. “Why do you have these” we would ask, teasingly, knowing full well why he had them. My friend’s face would grow bright red and we would stammer: “because they’re so funny”. When pressed, he would double down on it: he would swear, up and down, that he had them because they were hilarious. Sure they were.

It puzzles me, why we act as if it’s even possible that verbal abuse on the internet is “just a joke”. A decent response to “it was just a joke” is “it doesn’t matter”.

The biggest mistake we ever made with trolls was making the question of abuse about how to placate and fix them instead of how to empower the people they hurt or manage your own well-being in the face of them. Like so many abused people, we thought the solutions involved walking on eggshells and not provoking them back. But instead, we must acknowledge “that we are what we pretend to be, so we must be careful about who we pretend to be.” And that means acknowledging the awful, terrifying power of jokes and the immunity we seek in “not being serious.” This is exactly why people troll in the first place. Because deep down, they know it’s serious, and that’s exactly why it makes them feel powerful.

In the online world, people who violate community standards should be banned from those communities. Gathering spaces online are not public spaces: almost all of them are owned by private companies or individuals. Freedom of speech is up to the owner of the space; the level of discourse there directly reflect’s the owner as well. By law, they might not be legally responsible for the content of their site, but they are ethically and morally responsible for it, regardless. Owning and running a site where terrible things happen should be a black mark on a company’s or a person’s reputation—and that should matter.

It would be nice if people started to care about reputation again, and if bad reputations led to lower profits and lower stature in the global community. Sadly, we are in a time, right now, where that does not seem to be the case.

Re-committing to Pinboard, after many months away

I’m re-committing to Pinboard, after a year or more away from it. I’m happy with what I am doing now, and thought I would document it in case anyone else wanted help understanding how to use the Pinboard effectively, especially if their usage lapsed, as mine did.

What is Pinboard?

Pinboard is an “antisocial” cloud bookmarking service. You can keep all your bookmarks there and use its barebones website or third party apps and browser extensions, all using an open API, to access them. It’s a paid service, run by a single person, with a clear and straightforward business model. When I signed up, I pre-paid for ten years of service. Part of my impetus for using it again now, after having abandoned it for, well, nothing, is the sunk-cost fallacy. The other, more important part of that impetus is that I really like the simplicity and speed of Pinboard, and I like the Pinboard iOS client I use, Pinner.

What do I use it for?

I use it for three things:

  1. To host my bookmarks in a cross-platform, always accessible way. I can get to the same bookmarks in Safari on my Mac and iOS, and in Chrome on Windows.
  2. For research and archival purposes, especially for programming projects I am working on. I can search these bookmarks on keywords, title, or description to review the best of the web pages I previously read on a topic of interest.
  3. As part of a homegrown “read it later” service. I can send article URLs to Pinboard from various apps, and read them later using Pinboard’s website, an app or browser extension on my Mac, and an app on iOS or Andriod.

Number 3 used to be the primary purpose of Pinboard to me. I had signed up as part of an effort to get replace Pocket with something more privacy focused. After many years of using Pocket (formerly “Read It Later”) to collect articles I was interested in from the huge stream of RSS feeds I parsed every day, I wanted a change. Primarily, this was because I became uncomfortable with Pocket’s business model: Why was it free? How did they really make money? What were they doing will all the data they collected on me?

I also wasn’t crazy about some of the UI changes made to Pocket over the years. I wanted more control over the reading experience, too, which is something that using a web service with an open API would give me. It helps that, at the time, Safari’s Reader View debuted, and I thought it was fantastic.

I was pretty obsessive about channeling all the articles I read through Pinboard, so I had a one-way workflow from discovery to reading to marking read. I never deleted anything from Pinboard, either. I thought I wanted a history of all the articles I ever read, in case I wanted to search through that history later. (Of course, I never did that.)

Why did I stop using it?

I stopped using Pinboard for three main reasons:

  1. I started reading Twitter more than actual articles linked to from it. The constantly updating timeline was incredibly addictive, and less mentally taxing to follow than reading complex articles from actual publications. (I have since given up Twitter because it was too addictive for me to handle responsibly.)
  2. My wife and I had kids, meaning that I no longer had a bunch of downtime after dinner to catch up on all the articles I had bookmarked to read later. I would still send stuff to Pinboard to read later, but I would never actually read the articles.
  3. My wife and I subscribed to The New Yorker, The New York Times, and The Washington Post. I started reading from those publications, from their apps, a lot more than scouring RSS feeds for articles from a dozen sources. Reading from their apps did not fit very well with my Pinboard workflow.

Overall, Pinboard became a graveyard for links I didn’t actually want to read. Instead of a useful resource, it was a junk pile full of stale content.

Digging out of a mess

I took the following steps to return Pinboard to a useful utility for me:

  1. I deleted everything I had in Pinboard—over 3,000 bookmarks that were doing me no good. Most of these were articles I imported from my RSS reader (Reeder) or Twitter (via Tweetbot), read once, and then just left in Pinboard.
  • I installed Shiori on my MacBook Pro. Shiori is a Pinboard bookmark launcher and editor. It’s like QuickSilver for Pinboard—hidden until you need it, only a keypress away, and accessible from anywhere. I set it up so that Control+Option+Command+P brings up the bookmark search window (from anywhere), and Control+Option+Command+B brings up the bookmark editor.
  • I set up Pinner on iOS. Pinner is a full-featured Pinboard client. It will open Pinboard bookmarks in Safari or within Pinner, via Safari View Controller. It has two app extensions for creating bookmarks. The first extension, “Quick Pin”, has no UI, and is for quickly adding bookmarks to read later. The second extension lets you edit, interactively, all the metadata associated with the bookmark prior to saving it.

My workflow

I developed a new workflow to work with Pinboard, so I don’t end up with a mess of useless bookmarks again. Honestly, though, calling it a workflow is an exaggeration. I basically decided to manage Pinboard with a simple set of rules.

I will continue to use Pinboard both for permanent bookmarks, which mostly involve specific technical documentation about Swift and iOS development, and for a read-it-later service, which are bookmarks I want to keep around temporarily, some of which I plan to keep long term.

  1. Bookmarks I would keep in Safari, for sites I would log into (banking websites, personal websites, blogs, GitHub, BitBucket, etc.), are stored as private bookmarks with tags. All of these bookmarks are also tagged “Safari” so I can pull them all, as a group, with a Pinboard search.
  2. Bookmarks for articles to read later, and everything else, are saved with the “read later” flag set to “true”, primarily by using Pinner’s “Quick Pin” extension or the “send to Pinboard” command within Reeder (my RSS reader app of choice).
  3. I regularly use Pinner or Shiori to browse my “read later” list. Basically, I had to kick the Twitter habit.
  4. After I read articles marked “read later”, I delete the bookmark, or choose to save it. I am pretty ruthless about deleting bookmarks now, which is the opposite of how I used to be. If I don’t read something after a few days, I will just delete it.
  5. Rarely, I will choose to save the bookmark. If I do so, I edit the bookmark’s metadata to remove the “read later” flag and to add keywords and a description. I open copy the first paragraph of the article to the bookmark’s description field, so it’s clear to me later on why I saved it.

So far this workflow has been working well for me. I collect “read later” bookmarks throughout the day, read through them in the evening, and delete almost all of them at the end of the day. My Pinboard bookmarks list is much smaller than before, but contains only good stuff that I want to act on, either now or later.