Querl

There’s a corporate-approved blog post over at my company‘s blog, so I’ll keep this real brief: after a great deal of corporate wrangling, I managed to open-source the GraphQL library we build and have been using internally after finding no good lightweight alternatives to Apollo. It’s called Querl, it’s easy to use, you can install it with SPM or Cocoapods, and there’s decent documentation in the README. Check it out!

Posted in Code | Leave a comment

Web Open Font Format (WOFF) and UIFont

A few months ago I tweeted this:

Now that this change has actually been in my company’s app for a while, and nothing has blown up, I thought I’d expand on it a little.

The important takeaway–the tl;dr, if you will–is that you can simply compress your ttf fonts into woff, drop them into your app, change the file name in your plist, and you’re off to the races. Our app only has a few fonts, but it’s fairly small overall, so that savings is proportionally significant. If you have a small app, or if you have a lot of fonts, well, there’s no downside that I can see.

How did I figure this out? I wish I had a story about digging around in Hopper looking for optimizations. (And indeed my Hopper license is paid up.) But in this case it was just deductive reasoning.

  1. Using Emerge, I saw that the installed size of the app was fairly heavy on fonts.
  2. I googled “how to compress fonts” or something along those lines.
  3. I learned that Safari supports modern compressed font formats.
  4. I reasoned that it was unlikely WebKit had its own font code path, so if Safari can do it, maybe other parts of the system can too. (I could have verified this, since WebKit is open source.)
  5. I fucked around and found out.

If there’s a lesson here, it’s not to let received wisdom limit you. The documentation doesn’t mention this, and it’s very possible the right confluence of engineers within Apple has not come together–it might be that no one is in a position to write that documentation! But so what? Give it a try and see what happens! Coding is all about exploration, right?

Posted in Uncategorized | Leave a comment

Handling Empty Lists with Realm’s Property Wrappers

At work we recently updated from a pretty old version of Realm (10.8) to a pretty new version (10.20). Despite being just a point release, there have been some significant changes. I was very excited that the library is taking advantage of property wrappers to get rid of the cumbersome @objc dynamic decorators on every model property. We jumped in to converting all our model classes, and ran into one significant issue.

An interesting quirk about our REST API is that rather than return an empty JSON array when a model has an empty property (like { "things": [] }) it will sometimes omit the key entirely. This makes sense from the backend point of view, but it’s a challenge when we want to reduce optionality on the client side.

This wasn’t a problem for us with Realm’s old syntax, because due to some quirk of the library’s init(from decoder: Decoder) implementation, it would respect default property values rather than crashing. So by declaring our List properties like so:

var things = List<Thing>()

We ended up getting the right result either way: an empty list whether or not the API included the corresponding empty array in its response.

Unfortunately, this interesting quirk does not apply to Realm’s @Persisted properties, which go through a different decoding path. Instead, after much trial and error, we realized that we needed to override KeyedDecodingContainer‘s decode function for the specific case we care about (a persisted RealmCollection), like so:

public extension KeyedDecodingContainer {
 func decode<T>(_: T.Type, forKey key: Self.Key) throws -> T 
  where T: Persistable, T.Value: RealmCollection, T: Decodable {
 try decodeIfPresent(T.self, forKey: key) 
     ?? Persisted(wrappedValue: List<T.Value.Element>() as! T.Value) as! T
 }
}

public protocol Persistable {
 associatedtype Value: _Persistable
}

extension Persisted: Persistable {}

What’s going on here?

KeyedDecodingContainer is part of the Decodable implementation chain, responsible for holding and providing access to decoded values.

This decode function that we’re overriding is responsible for decoding values of type T, which we’re requiring to be a Persisted struct–that is, a model property that’s declared with Realm’s @Persisted property wrapper. (The Persistable/Persisted dance is just a weird requirement of the type system and can safely be ignored.) We’re further requiring T to have a Value type, that is, for the property wrapper to be wrapping, some kind of RealmCollection. All put together, that means we’re overriding the decode implementation only when the system attempts to decode something declared like @Persisted var things: List<Thing>.

Once the system has decided that’s what it wants to decode, our implementation kicks in. We call into decodeIfPresent in order to account for the case where the array is absent without throwing an error. And we use the nil coalescing operator ?? to provide our own value in that case. What value do we produce instead? Why, a Persisted struct wrapping an empty List, just the type we were trying to decode in the first place. We have to do some casting to make sure we produce exactly the type expected, but it ends up being the right thing.

This took some effort to figure out, but once done, it’s comforting to have the quirks of the decoding process under our control rather than depending on an undocumented feature of the library. Hopefully this blog post will help the next person figure it out a little more quickly.

Posted in Code | Tagged , , | Leave a comment

User Language Preferences

Here’s a quick one. Google has a neat library for (among lots of other things) on-device translation, called ML Kit. I’m trying to implement it for a hackathon. Turns out (no surprise?) it’s written in Objective-C, and doesn’t really have great affordances to integrate with system libraries. In particular, the enum called TranslateLanguage doesn’t have an initializer that takes a language identifier or locale identifier. I went about this in what I thought was the obvious way, using Locale.current.languageCode to initialize a new TranslateLanguage. But of course the current locale isn’t necessarily the same as the language the user has chosen. For that, I found this to be the simplest way:

extension TranslateLanguage {
    init?() {
        guard let preferredLanguage = Locale.preferredLanguages.first,
              let languageCode = Locale(identifier: preferredLanguage)
              .languageCode else { return nil }
        self = .init(rawValue: languageCode)
    }
}

Posted in Code | Leave a comment

GraphQL

I’ve been lazy about blogging for the past, oh, five years or so, but I just published a new post about GraphQL over at my company’s blog. Check it out if you want to know why Apollo is bad and what you can use instead.

Posted in Uncategorized | Tagged , | Leave a comment

Mistakes Were Made: A Brief Note on Keyboard Full Access

If you’re like me, you just spent several days refactoring a framework shared between a host app and its app extension–say, a keyboard extension–into its own project and repository. You finally got everything building again, resolving all the broken framework search paths; then you got everything running again, resolving all the broken bundle paths; but now you’re seeing errors like this when you try to use UserDefaults or the keychain on a device:

2019-01-06 21:29:04.948196-0800 Keyboard[7501:295823] [User Defaults] Couldn't read values in CFPrefsPlistSource<0x163d75700> (Domain: group.com.xxxx.keyboard, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd

2019-01-06 21:29:04.966103-0800 Keyboard[7501:295823] [User Defaults] Couldn't write value for key test_key in CFPrefsPlistSource<0x163d75b80> (Domain: group.com.xxxx.keyboard, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): setting preferences outside an application's container requires user-preference-write or file-write-data sandbox access

Weird, right? You googled these errors and found just a couple of things related to sandboxing on macOS, and a bug in a beta iOS release a few years ago. You went down the rabbit hole. You tried adding entitlements to your newly standalone framework. You tried code-signing it. You tried removing and re-creating your App Group.

Then you retraced your footsteps. Started from scratch. And a couple of days into that, you realized that what you didn’t try, after cleaning your entire environment and resetting your device, was turning the keyboard Full Access setting back on.

You’re welcome.

Posted in Explanation | Tagged , , , , | Leave a comment

DYJ4U on iOS Autocomplete: a Response

Jason Clauß, self-described “UX Renegade,” recently published an article on the iOS keyboard’s suggestion bar: Doing Your Job For You: The iOS autocomplete. I have some thoughts.

I strongly disagree with the conclusions Clauß draws. They’re based on his own “cursory research;” I am certain that Apple has done a fair bit more than that. But I also think he’s just wrong about the new design creating a clearer emphasis around the center word. New: the same font color as the other words, with a 20% background contrast, versus old: a different (classically active, as in links) font color, with a 25% background contrast? That is less emphasis, not more.

The fact that the new design is less eye-catching, rather than more (as he wants), tells me that that sort of emphasis is distracting for the user, not useful. Which makes sense–the goal of autocorrect is for it to “just work;” the more attention you have to pay to the suggestions bar, the less that goal is being met. It’s fair to ask whether that goal is being achieved, but it’s clear that Apple is trying to make it so people just don’t need to perform the context switching that Clauß wants to make easier.

Fundamentally this strikes me as a remarkable misread of Apple’s design philosophy. Ask yourself, what seems like a more Apple-y end goal? A future in which the autocorrection and suggestion works so well that you don’t even need to see a suggestion bar, as the right words just appear at the right times in response to your poor typing? Or a future in which the suggestion bar is hyper-prominent, and it’s really simple to choose which word you need at any time? Historically Apple prefers to give a user fewer choices, not more.

This may seem catty, but I can’t help but think Clauß composed this whole argument without making the contrast comparison, realized that the new design is actually lower contrast, but went ahead and hit publish anyway.

Posted in Philosophy | Tagged , , , , , , | Leave a comment

Recursive Property Observation in Swift

Over the last couple of years, I’ve been writing almost exclusively Swift. It’s been quite a roller coaster. I still don’t feel as though I’m an expert in the language, but I am getting to the point where I quickly identify its quirks, and can make a reasonable effort to debug them.

This one came up fairly late in the process of building Jot. Although it’s a simple app, feature- and screen-wise, there’s still some complexity there. Eventually there were enough potential things that could happen when the app opened—authenticating with FaceID or TouchID (and the associated failure states); starting a new note, either automatically or from a deep press on the app icon; opening a note from Spotlight; displaying a reminder—that the best choice was to build a state machine specifically for foregrounding. This was a great move! I kept all my logic in one place, and it was really easy to figure out how I moved through the various states from launch to default screen.

I ran into trouble when I needed to move through states without user intervention between. I wrote it something like this:

enum StartState {
	case unauthenticated
	case authenticating
	case authenticationFailed(error: Error)
	case authenticationSucceeded
}
var startState: StartState {
	didSet {
		switch startState {
			case .unauthenticated:
				if UserDefaults.standard.requiresAuthentication {
					startState = .authenticating
				} else {
					startState = .authenticationSucceeded
				}
			case .authenticating:
				authenticate() // invoke Apple's auth, and eventually set a different state
			case .authenticationSucceeded:
				authenticated = true // we're done
		}
	}
}

Well, it turns out that after entering the unauthenticated state, the flow stopped. This is because Swift’s property observations are not recursive: once you’re in the property observer, further changes to the property won’t trigger the observer again. It’s easy to imagine why: in a simpler property, like

var doubleWhatImGiven: Int {
	didSet {
		doubleWhatImGiven *= 2
	}
}

This will cause a stack overflow. Nobody likes that. But I’m convinced I know what I’m doing, and fortunately Swift does give me enough rope to hang myself. All that’s necessary is to use an intermediate function, like so:

var startState: StartState {
	didSet {
		respondToStartState()
	}
}

private func respondToStartState() {
	switch startState {
		case .unauthenticated:
			if UserDefaults.standard.requiresAuthentication {
				startState = .authenticating
			} else {
				startState = .authenticationSucceeded
			}
...
}

And everything works great! One level of indirection is enough to convince the runtime that it ought to invoke the property observer again, even in the same run loop.

Posted in Code | Tagged , , | Leave a comment

Jot

Hi folks. Joel here. It’s been a while since this blog has been up to much, hasn’t it? Well, after a lot of privately-owned work about which I was not meant to blog, I’ve got something I can talk publicly about. A little bit ago my friend Matt and I released Jot, a note-taking app for the bells-and-whistles-averse. This isn’t Jot’s blog (that’s here), so I’m not going to give you the sales pitch. (Although I will say it’s good and you should buy it.) Instead, I’m going to talk a little bit about the technical side of building this app. I’ve got a few topics lined up that might be of interest to iOS developers. I’m a little rusty at this blogging stuff; we’ll see if I manage to make it through the list.

For now, we’ll start at the high level: how does one make a new app today? A lot has changed in the ten years since the iPhone SDK came out. But Jot is unusual in that it has no backend component. That means there are no API calls, not a lot of asynchronous work, and relatively few failure states. In this sense, it’s a lot like apps used to be back in the good old days.

The things that have changed, though, have changed for the better. When we started making iPhone apps, GitHub wasn’t there to provide libraries for every occasion. Dependency management was done with Git submodules or SVN externals. Core Data was unpolished and unopposed. There were no nibs, xibs, or storyboards. Objective-C was the only available language, and MVC ruled the roost.

Jot uses Carthage for dependency management, though there are not all that many dependencies to manage. The main one is Realm; as a note app, Jot is essentially just an interface on a database, and I prefer Realm’s APIs. (The lack of a FetchedResultsController-analog is still quite painful, though.) I played around with using IceCream for iCloud-based syncing, but it’s still a little too early to be reliable. It’s written in Swift 4, and makes use of plenty of extensions and protocols. I’m not on board with storyboards, so xibs specify the interface. (As far as I’m concerned they’re at the sweet spot between WISYWIG heaven and merge hell.) The app is architected with the coordinator pattern, which I’ve been cheerleading for ever since Soroush started writing about it. Three coordinator-based apps and counting!

That’s a brief overview of the technical decisions that went into Jot. In coming posts I’ll take a more in-depth look at a lot of those pieces, and talk about how they come together. I hope you stick around.

Posted in Code, Software | Tagged , , | Leave a comment

Repartee

icon1024Spaceman Labs was founded on one principle: that we are two extremely debonair individuals capable of making extremely excellent iOS apps. Today we advance this hypothesis by introducing our flagship app, Repartee. In truth, Repartee is the app we created this company to build. We brought our iOS expertise and keen design sense to bear, and added new competencies all over the place—particularly in creating a server application to complement the iOS client.

But I’m getting ahead of myself.

What is Repartee?

Repartee is the best way to plan your parties, participate in your friends’ events, and appreciate the memories afterwards. It has a slew of features, a selection of beautiful stationeries, and some really cool technology under the hood. Best of all, it’s completely mobile. Creation, hosting, attendance, sharing—it all takes place on your iPhone. (There’s a thin web client for guests who don’t have iPhones, of course.) Oh, and because we’re a startup with more dreams than business sense, it’s completely free.

Get it get it!

Sounds great, right? Before you read any further, go download Repartee here. Give it a try, then let us know what you think. If what you think is positive, do it in the form of an App Store review. It’s amazing what a big difference that makes, so please, if this blog has ever been helpful to you, consider leaving a review to be paying back the favor.

Features

Because this is primarily a development blog, I’ll talk a little bit here about some of the cool things we have going on that aren’t immediately apparent to the end user.

Creation

  • Every event needs a location. We use Foursquare’s excellent API to auto-complete venues when possible. If you’re having your birthday party at the W Hotel, you shouldn’t have to type more than a few characters.
  • We want to encourage our users to try out different stationeries to find the best one for their event. To that end, all the details you enter into a stationery template are saved. They’ll be applied to each new one you try out.
  • Repartee is time zone-aware. If you plan an event in a different time zone, it will ask to make sure your start and end times are in the venue’s time zone, and adjust them if needed.

Inviting

  • Repartee invites your friends no matter what contact information you have for them. Email, text message, or Facebook—the experience for the user is the same. We handle the work differently on the backend, which no one needs to care about.
  • When you select a contact, Repartee checks a secure hash of their contact information to see if they’re already on the platform. If so, they’ll receive a push notification, and we can additionally contact them through whatever means they prefer.

RSVPs

  • Our emails have links to open on the website or in the app, if it’s installed. If you visit the site on a desktop, we provide a simple means to transfer the event information to the app on your phone, even if you’re not signed in with the same contact information the host invited. There’s a really neat flow on the app for this—check out the “Find an Event” button.
  • RSVPs include notes for the host (e.g., “I’ll be a little late but I’m looking forward to it!”), and can be changed at any time as circumstances require.
  • The list of invites color-codes guests by RSVP status. The host can mark individuals as VIP by tapping on their photos.

Attendance

  • When VIPs arrive, the host will receive a push notification.
  • If guests take photos at the event—same time and place—they’ll be allowed to turn on auto-uploading for a huge ease-of-use boost. They do get to review which existing photos will go up when they turn it on, and can deselect embarrassing ones.
  • Repartee tries to keep guests engaged in both the event itself, and the second-screen-esque experience taking place in Repartee, through the judicious use of push notifications. This stops when they leave the event.

Photos

  • The photostream is optimized for exploration. More interesting photos are bigger, to invite interaction.
  • Photos can take some time to pull down on a slow network, so the data model includes a size and average color. Repartee will show a color block of the right dimensions, and seamlessly fade in the photo when it’s downloaded.

Dashboard

  • One of our core competencies from our Roambi days is charting, which means we know exactly how much work it is to build a flexible, durable charting library. That’s why we opted to use third-party charts.
  • We considered the balance of information versus privacy very carefully. The host gets to see a lot of data in the dashboard, but anything potentially embarrassing is anonymized. The aggregate charts are the fun part, anyway.

I hope you’ve enjoyed this brief tour of some of Repartee’s hidden technical gems. There’s a lot more in there, and we’re continuing development at a rapid clip. Stay tuned here for bits we’ll open-source, lessons learned, and so on, as we have the chance. Even better though, keep an eye on the release notes as we push out new versions. And don’t forget to leave a review!

Posted in Software | Tagged , , | Leave a comment