Scorebook 1.5 Retrospective

A couple of weeks ago I brought my app back from the dead. Scorebook was first released in November 2014, got a minor bugfix update a month later and then was unceremoniously removed from the App Store by Apple in January 2018. Apple doesn't want apps that appear dead on their store anymore and with good reason. By all external accounts my app wasn't under active development.

And it wasn't. Over the last couple of years my desire to play games had gone down because I didn't want to use my app with the state it was in. Mid-last year I even joined a beta for an app under development that was very close in functionality to Scorebook and I got sad. I didn't want to use their app; I wanted to use mine. So I got to work.

This is going to be a retrospective of what it took to bring Scorebook back, culminating in the release a couple weeks ago of v1.5.

Grab Game Image From Web

Scorebook has a feature that enables finding box art on the internet and attaching it to a game. I built that against a web API (Bing) that performed the image search and returned the results. That API disappeared, and its replacement was migrated to Azure. I spun up an Azure account and set up their image search API. Past me would have called it a day from there.

Instead, with my experience I've gained over the past few years, I knew that if I hit my own API I could prevent this from happening to future versions of the app. I've been playing around with the Vapor framework (which is written in Swift) to power the web service, and using their cloud service was able to bring my API online pretty easily.

The most interesting part of this was thinking through authentication. I don't store any user data – don't want to, and don't plan on it– but wanted only my app to be able to talk to the service. I also know that baking a key into the app is fragile. So I utilized the public database provided by CloudKit to store the record. On launch the app performs a search for the key, which it then uses to authenticate to the server. The server likewise has the key and uses a middleware class that I wrote to validate the key sent as part of the request. I know it's not bullet proof but it gets the job done for what I need and I'm happy with the solution.

On Memory and Images

When a fried of mine was using the app after it first launched he told me about a crash he saw when taking pictures during a gameplay. Being the young developer I was, I looked at Crashlytics, didn't see anything, and moved on. It wasn't until later that I learned that out of memory crashes aren't picked up by crash reporting tools other than Apple. I decided to look into it and hunt it down if indeed it was a problem. (Narrator: It was)

I went through and audited all the places in code where I display an image. Save searching for a game image, all images are loaded from files on disk (though that did change in this version from the files being binary blobs in Core Data). When I needed to show a picture I'd simply create a UIImage from the data and put it in an image view. Done and done. Except that leaks memory like crazy and can result in the system killing the app. Whoopsie.

Thankfully there were a couple great guiding resources that came out this year: WWDC 2018 session 219 (presented by Xcoder Kyle Sluder – once an Xcoder always an Xcoder), and a very helpful post by Jordan Morgan going into some extra detail on how to get it done.

I set about adding a downsampling method like the one at WWDC, and had to defer actually rendering the image until I knew the frame that it needed to fill. Sometimes it's a small circle representing a person's image, sometimes it's a picture taken during a gameplay. I don't make assumptions about that until I know all the conditions I need. Images are stored inside of the <Container>/Documents/Pictures/<ImageType> directory, so knowing the image name (which is stored in the Core Data entity) and the type of the image I need, I could assemble the URL to the image file on disk.

From there I hand it, along with the size needed, to the downsampler, and it hands back the scaled image. I had to get rid of essentially all of my [UIImage imageWithData:] calls in favor of this new method. It was a fair bit more work than I thought it might be, but wound up saving a ton of memory so it's completely worth it. Plus it was the right thing to do.


Those are the two things that took the longest to get done, and there are a handful of other things that made it in to this release from times over the last few years when I did some concentrated work on Scorebook. The big ones were architectural things like splitting up the main storyboard into multiple smaller ones, and refactors to have the app flow use coordinators.

I've also been modularizing things a bit as I'm working to get sync turned back on. We take that modular approach at Lyft and I really have enjoyed the benefits.

Final Thoughts

One thing I wasn't expecting was easily slipping back into my old mindsets as I approached a problem that I had set aside for a while. I have to knowingly refuse to let myself get caught up into old habits, or think that a problem is too big. If it's too big it just needs to be deconstructed further until it's in manageable chunks that I can tackle one at a time.

I'm incredibly happy to have Scorebook back on the App Store. Bringing it back to life has been a lot of work, but I've been able to look back at the decisions past me made (good and bad), and apply some of what I've learned at my day jobs as well.

Go forth and remember your games!

Mixing Swift and Objective-C in a Framework

If you've worked on an iOS app that has both Swift and Objective-C code you're likely well familiar with the rules for getting the two languages to talk to each other. A bridging header here, some @objc declarations there, and you're probably good to go.

But the rules change a little bit when you want to start breaking your code up into frameworks and suddenly there's no bridging header. So how are you supposed to get your mixed-language framework target working anyhow? Fear not, you've come to the right place.

For this example let's say we have a language learning app called BiLingual, and it has a framework target called LanguageKit.

Setting Things Up

The framework target needs to be configured to build as a module (using the DEFINES_MODULE build setting). It also needs to then define a module name (the PRODUCT_MODULE_NAME build setting). Usually you won't have a custom name, but that build setting is what you'd use if you want to.

Objective-C into Swift

Your Swift files that belong to the your framework target implicitly import the target. Where app targets use a bridging header to talk to Objective-C code, frameworks rely on the umbrella header – LanguageKit.h in our case. The umbrella header imports the headers you want to be made public like this: #import <LanguageKit/Language.h>. This style of import is a modular import.

Any header imported into the umbrella header can now be seen by Swift files belonging to your framework target. Sweet!

Swift into Objective-C

Like apps, frameworks rely on a generated header to expose Swift code to Objective-C. In our app we would import its generated header like so: #import BiLingual-Swift.h. But, in our framework files we need a modular import like this: #import <BiLingual/BiLingual-Swift.h>. Please, please, please only import these generated files in your implementation files. Importing them into headers is only asking for trouble.

From here the standard rules apply. Your Swift classes need to be NSObjects and members need to be decorated with @objc in order to be seen by Objective-C. The one other thing to note is that the Swift types need to be public in order for visibility to Objective-C. I honestly don't know why this is at the moment. I'm hoping that someone smarter than me ca point me to the reasoning for this. If you know, please drop me a line.

Private Objective-C into Swift

If you're like me, you're a stickler for having clean API boundaries. This means you may have some Objective-C classes that you don't want framework consumers to see but your Swift files might need them. Thankfully there is a good way to do this. You'll create a special module that is only visible inside the framework. Don't worry though, it's not as intimidating as it sounds.

First, in your framework's root, create a file called I don't know why it has to be this exact title, but it does. Here's its contents:

module LanguageKit_Private {
    // import your private headers here
    export *

We're making a module called LanguageKit_Private, and importing our private headers into it. There an extra build setting we'll set for this to work: SWIFT_INCLUDE_PATHS = $(SRCROOT)/LanguageKit. This tells the build system to look for additional module map files. From there in our Swift code, we can import LanguageKit_Private and the headers imported there are now accessible by Swift. 🎉


We've seen how to import public and private Objective-C into Swift, and public Swift into Objective-C. So go forth and worry no more about mixing your framework target languages!

Scorebook is Back!

TL;DR: Scorebook has returned to the App Store!

Scorebook got removed from sale by Apple in January of last year. I spent most of the year sad about that, and late in the year I asked myself a big question: Had the time come to let it go?

Screw that noise. Let’s get to work.

I went about updating it to require iOS 12. There were new devices that it needed to support, safe areas to work with, and an entire web API to replace. I’ve done a lot of work calling web services over the last few years at my day jobs. But I’ve never built one before.

After a lot of head down time over the last month I’m so happy to report that Scorebook 1.5 was released this morning. I’ve got a lot of work I want to do on it including finishing up the CloudKit sync I built out, updating the UI to be less 2014 and more 2019, and figuring out a different pricing strategy (hello in-app purchase!).

The most important thing is that Scorebook is back. I hope you check it out, play some games with your family and friends, and make memories over your games.

Check out Scorebook from Taphouse Software


In the second grade my dad took me to my first Mariners game. It was in the Kingdome against the Blue Jays and I really had no interest in going. We got there after the game started and got to our seats to the right of the press box. At some point during the game a foul ball came up to us (hit by backup catcher Matt Sinatro). A guy near us caught it and handed me the ball. I was hooked.

That was my entry into sports. I loved the Mariners and couldn't get enough of them. Fast forward to today. Last season was a huge disappointment; blowing another lead to some historic A's team is old hat. Thankfully we are going on a plan to tear things down and rebuild in a way that might actually get us competitive in a few years (I'm all aboard this plan FWIW – it's the only way we're going to ever compete for a World Series).

But last week a story came out that is just gross. I feel gross in wanting to cheer them on to win it all.

For about a year, the Mariners employed Dr. Lorena Martin. She came to a new role with the team as a "high performance trainer". She was let go from the team last month and is now saying that GM Jerry Dipoto, Manager Scott Servais, and Farm Director Andy McKay demonstrated clear racist tendencies, especially against Latin players. I don't know if her claims are true or not; the Mariners have fervently denied all the claims.

A lot of the stories she recounts seem to be personal meetings without third parties to back them up. So we seem left in a he-said, she-said position with no real insight of what to think. If it's true, Dipoto, Servais, and McKay need to go. Full stop. I don't want them leading the Mariners, and I don't want to cheer for a team they lead. But if it's not true then Dr. Martin should say so immediately.

As a fan I don't know what to think. I just want the truth to come out. And to root on my Mariners. One day they will win the World Series and I want to feel good about cheering them on to it.

The Ultimate Guide to iOS Notification Lifecycles

I work on the notifications team at Lyft for my day job. One of the things that comes up frequently is the question of how can we trigger our code from a push notification (or even, “what is a push notification”). I’ve had these chats enough that it seemed relevant to put together what I hope to be the definitive answer. I hope it’s useful to you.

Here we go.

What is a user notification?

Let’s start small. A user notification is a way for app developers to let their user know that something in the app warrants their attention. Users can be notified in any combination of three different ways: an alert (most commonly a banner that drops down from the top of the screen), a sound, and/or a badge.

Apps must ask users for permission to show these notifications before anything can be displayed. Oftentimes apps will show their own custom view asking for permission before displaying the system dialog. This is because an app can only prompt their users once for display of notifications. After that, the user must go to the app’s preference screen in the system

How can user notifications be triggered?

Notifications can be scheduled as a local notification, or delivered via push. Both types of notifications can do the same things, appear exactly the same to the user, and as of iOS 10, Apple unified the notification system under the UNUserNotificationCenter suite of APIs. This makes handling of notifications the same.

What is a push notification?

A push notification is a notification that comes in remotely. It may or may not display any content (ones that do not display content are referred to as silent pushes). It is important to note that apps do not have to ask the user’s permission to deliver push notifications. Silent pushes do not require user permission.

You must register your app with the system in order to send a push. This is done with UIApplication.shared.registerForRemoteNotifications(). You’ll be called back with the result of this by implementing application(UIApplication, didRegisterForRemoteNotificationsWithDeviceToken: Data) for success, and application(UIApplication, didFailToRegisterForRemoteNotificationsWithError: Error) for failure. When the registration succeeds, you’ll get a device token that needs to be registered with your server that you’ll use to send a push.

When does my code run via push?

This is going to get complicated. The short answer is: it depends. But you didn’t come here for short answers, so let’s dig in. We’ll start by looking at an actual notification payload. I’ll put in the stuff we need, but you can find the full payload content reference here.

  "aps": {
	"alert": {
	  "title": "Our fancy notification",
	  "body": "It is for teaching purposes only."
	"badge": 13,
	"sound": "fancy-ding",
	"content-available": 1,
	"category": "CUSTOM_MESSAGE",
	"thread-id": "demo_chat",
	"mutable-content": 1

If you were to send this payload to a device, the user would see an alert with the title being the aps.alert.title value, the app icon would badge with the aps.badge value, and the sound would be the aps.sound value (assuming all the permissions were granted by the user).

You might want to send a notification that wakes up your app to fetch some content in the background, and a silent push is perfect for that task. This can be accomplished by setting the aps. content-available field like the example. When the system receives this notification it may wake up your app. Wait, may wake up? Why won’t it always wake up the app?

This brings us to the uncomfortable reality: the payload contents as well as the app state determine when your code is activated. Let’s look at a chart:

App State Payload Result
Backgrounded aps.content-available = 1 application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
Running aps.content-available = 1 application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
Killed aps.content-available = 1 Nothing
Running aps.content-available = 0 Nothing
Backgrounded aps.content-available = 0 Nothing

So, if your app is running or has been backgrounded, you’ll be called. But if the user has killed your app, nothing happens. Bummer! The next question you may ask is if there is a way for your code to run every time a notification comes in. Indeed there is.

Apple gives us two different extension points that let us run code when notifications arrive: UNNotificationServiceExtension and UNNotificationContentExtension.

Notification Service Extension

With the service extension, the intention is that you are going to mutate the content of the extension before it is presented to the user. The typical use cases are for things like decrypting an end-to-end encrypted message, or attaching a photo to a notification.

There are 2 main requirements that your notification must meet in order for this extension to fire: Your payload must contain both aps.alert.title and aps.alert.body values, and aps.mutable-content must be set to 1. The payload we have above satisfies both of those requirements and would trigger our app’s service extension if it had one.

It’s important to note that you cannot trigger a service extension with a silent push. The intention is for the extension to mutate a notification’s content, and silent pushes have no content.

Payload Result
aps.mutable-content = 1 && aps.title != nil && aps.body != nil Extension Fires
Anything else Nothing

Notification Content Extension

The other extension type allows us to paint a custom view on top of a notification, instead of relying on the system to render the notification. Users get to see this custom view when they force press or long press a notification (or in Notification Center, they can swipe left on a notification and tap “View”).

When you include one of these with your app, the extension’s Info.plist file declares an array called UNNotificationExtensionCategory. Each of these categories maps to a notification’s aps.category value. To go back to our sample notification above, our app would have to register a content extension for the CUSTOM_MESSAGE category.

Payload Result
Extension’s UNNotificationExtensionCategory contains aps.category Extension Fires
Anything else Nothing

One more thing

There’s one last bit that I want to cover: The UNUserNotificationCenterDelegate protocol. A conformer will get called in two main cases: when your app is in the foreground and will present a notification, and when a notification is tapped on.

When the user taps on one of your notifications, the app will fire up and come to the foreground. It’s a good idea to register the notification center delegate at startup so you’ll get the userNotificationCenter(_:didReceive:withCompletionHandler:) call. This lets you properly handle the notification, otherwise your app will just come to the foreground and that’s it.


We’ve come a long ways, looking over the kinds of notifications we can have (interactive and silent), the different transport mechanisms for them (local and push), and how all these ways can interact with our code.