For iOS devices: Sunset now recognizes touch events on its gamepad, and will use
those instead of click events if possible. This eliminates the 300 millisecond
input latency you otherwise get. This means it takes about 6 fewer seconds to go
from one side of a map to the other. Speedrunners rejoice!
For desktops and laptops: Sunset has long supported keyboards through arrow
keys, the space bar, and the escape key. Today I also added support for WASD for
movement and Q and E for turning. This is the first time you’ve ever been able
to strafe in Sunset. Such flexibility!
Back in 2007, Apple released the iPhone. Apple didn’t announce the App Store
then, saying instead that web apps would be the “sweet solution.” People weren’t
terribly keen on that, but I thought JavaScript had much to offer on the iPhone,
so I set out to make a game that pushed the boundaries as much as I could.
I decided to make a 3D turn-based RPG. Sunset was the result. I released it in
January of 2008, shortly before Apple announced the iPhone SDK. The first wave
of native games easily trumped my game’s visual fidelity, but for a very brief
window of time I honestly felt I had put something out that was at or near the
top of the heap of what developers could make the iPhone do.
Much has changed since then. WebGL is a thing, enabling browser games to far exceed
what I could do in a Canvas tag. Phones have gotten orders of magnitude
faster. I’m pretty sure my iPhone today is faster than the iMac I developed
Sunset on. In fact, because of how fast modern phones are, I made a minor change
to Sunset today before uploading: as a performance optimization in 2007 I only
did a 3D raycast against every fourth column of pixels, leading to a
stair-stepping pattern when
viewing walls at extreme angles. I have removed that resolution limit, so your
device will now raycast all 320 glorious columns of pixels. Marvel at the
fidelity!
I played the game again to make sure it still worked (age has a funny habit of
breaking old software, even though the software itself didn’t change). I grimace
at a lot of my bad art, but I think the gameplay—simplistic though it
be—holds up pretty well. I like the difficulty curve, level progression,
and item stats. You struggle for a bit, then get a nice weapon upgrade and feel
like an invincible God, then you move on to the next zone and things get hard
again.
So if you want to play a small, free game that might keep you busy for a few
hours, give it a try!
Maidenhead Converter 2.2.0 went live about a
week ago, and—due to a bug—2.2.1 followed soon after.
There are two features in these releases, and several bug fixes. I added the
ability to pick a format for distances, allowing you to choose between Metric
and Imperial. This is presently only used for the accuracy label when tracking
your location, but it’ll also be used for other features I want to add later.
I also improved VoiceOver accessibility support throughout the app. You can
navigate menus and work the keypads via VoiceOver. I don’t know if I really have
any audience for VoiceOver, but it’s a good thing to do and I wanted to practice
implementing it anyway.
2.2.0 also fixed a few very minor display issues. A few views weren’t aligned
well on all devices, and text could clip on some others. Also, iOS 9 will change
the system font from Helvetica Neue to San Francisco. When displaying text in
iOS, you can ask the OS to give you the “system font” of a specific weight and
size, but there were a few places where I was explicitly asking for Helvetica
Neue, so I replaced those calls with asks for the system font.
Unfortunately, 2.2.0 also broke the in-app purchase for the ad disabler. Folks
who already bought the ad disabler still had ads disabled, but people who didn’t
already have it couldn’t buy it.
How did this happen? Didn’t I say in my last post that I have a Runbook that I
use to test every part of the app? Yes, and I ran it for 2.2.0, and every other
part of the app worked, but the store didn’t, and I shipped anyway. Why?
Because I couldn’t find any reason in the code for the purchases not to work, so
I decided to bet that I wasn’t able to do purchases in dev because the sandbox
iTunes Store was down, which is something that happens from time to time. Once
the app went live and purchases didn’t work with the real App Store, I knew that
bet was wrong.
After poking around for a long time, I discovered the bug appeared when I
transitioned to Swift 1.2. That version of Swift added a built-in Set type, and
calls that used to take the Objective-C NSSet type changed to take the new Set
type, so I changed one line of code in the store from:
So all is good, yes? No. For some reason, despite the fact that the syntax is
valid, and logging out the resulting set appeared fine, for whatever reason when
that bridged back to Objective-C for StoreKit, the set was invalid in some way,
and a “could not connect to the iTunes Store” error would be produced. The
solution ended up being to change the line to:
Which also compiles fine and looks fine when logged out, and by all respects
should be functionally identical to the previous line, but it was the
difference between a working store and a broken one. I’m guessing there’s a
Swift bug here, and when I update the app to Swift 2.0 I think I’m going to try
both constructions and see if only one still works, and if so file a bug.
So the lesson I need to take away from this is that I really do need to pass all
my QA checks before shipping an app, no matter what.
Maidenhead Converter 2.1.1 is now live. This
was largely a refactor and bugfix release, there aren’t any new features.
I like using Maidenhead Converter as a testing ground for ideas, patterns and
practices that I apply to my work on EventBoard.
Maidenhead Converter is small and simple, but does a real task.
Recently, I’ve had a big focus on QA. The app now has a suite of unit tests, and
those tests helped uncover some of the bugs that I fixed. The bugs tended to be
on the app’s edge cases, but they were real, and I hadn’t caught them before.
I’ve found that internal data and processes are easy enough to write unit tests
for, but views and view controllers seem quite difficult to test without doing
some pretty bad contortions. I would like to figure out a good, straightforward
way to write automated tests for the UI layer, but I don’t have one right now.
What I do have is a Runbook. A Runbook is a document that describes every
button and feature and state of the app, and says how they should work. It’s
similar to the monolithic design document that software under the waterfall
development model gets, except the Runbook is developed alongside the app,
instead of before. Before submitting a release, you treat the Runbook like a
checklist, examining everything in the app and making sure it works as intended.
While obviously far more time-consuming to execute than automated tests, it
still did good for my confidence in the quality of the app.
Now that this release is done, I intend to do 2.2.0, which will include some
new features.
Brad Larson of
Sunset Lake Software wrote a
blog post
called “Why we’re rewriting out robotics software in Swift.” The article is a
worthwhile read, and explains that an audit of their Objective-C code history
revealed that approximately 40% of the shipped bugs would have been prevented by
using Swift. Which is really interesting.
I would like to focus on a different part of the article, however. It’s actually more of an aside
in the article itself, but it was something that really stood out to me: the
use of enums instead of NSError in Swift. Brad goes into more detail on the
subject in a mailing list post.
After having played with it myself, I think using enums for errors is a really
big win, and I intend to use the pattern in my own Swift code going forward.
What’s Painful About NSError
NSError is used in code built on the Foundation framework for recoverable error
situations. By merely receiving an NSError object, you know something went
wrong, and you can inspect the object to learn more detail about what happened.
A good writeup on NSError can be found at NSHipster.
Unfortunately, constructing and inspecting NSErrors can be cumbersome. To create
an NSError, you need to specify a domain and a code. The domain should be a
reverse DNS-style string, like “com.company.app”. The code needs to be an
integer that’s unique within the error domain. The domain and code tend to be of
limited usefulness when using NSErrors later, but you need to supply them when
creating NSError objects anyway.
The more-often useful information in an NSError lives in its userInfo
dictionary. You provide values for a few system-defined keys like
NSLocalizedDescriptionKey, and you can attach any additional objects you wish
to the same dictionary. I often need to refer to documentation to remember
NSLocalizedDescriptionKey, and attaching extra information to the dictionary
requires you to keep track of the keys you use in order to consume them later.
Picking apart an NSError very frequently involves trips to a header file or
framework documentation.
Using Swift enums nicely addresses these complaints.
A Swifty Enum Error
So what does an enum error look like? Here’s an example of a hypothetical error
type for interacting with an API server.
A function which can produce an error could look like this.
typealiasAPICallback=(data:NSData?,error:APIError?)->VoidfuncmakeAPICall(callback:APICallback)->Void{// Successcallback(data:NSData(),error:nil)// There was no internet connectioncallback(data:nil,error:.NoInternet)// There was an HTTP error codecallback(data:nil,error:.HTTPError(statusCode:404))// The server rejected the callcallback(data:nil,error:.ServerError(message:errorMessage))}
Creating errors becomes easy, clear, and descriptive. It’s even
statically-typed, so the compiler will require you to construct the error
correctly.
Consuming errors is simple, as well. You could use a switch statement to handle
some—or all—cases.
switch(error){case.NoInternet:println("You should get a better ISP")case.ServerError(letmessage):println("The server responded with message: \(message)")default:// Handle other error cases in a general way}
Often you just want a textual description of the error. You might want to put
the text into an alert view, or you might want to log out the error description
while debugging. This can be handled by having your error type implement
the Printable protocol.
enumAPIError:Printable{caseNoInternetcaseHTTPError(statusCode:Int)caseServerError(message:String)vardescription:String{switchself{case.NoInternet:return"There is no internet connection."case.HTTPError(letstatusCode):return"The call failed with HTTP code \(statusCode)."case.ServerError(letmessage):return"The server responded with message \"\(message)\"."}}}
Printing the error then becomes as simple as this.
println(error)
In my time with this style of error, I believe they are far easier to construct
and consume than NSError, so I quite like them.
What About Objective-C Compatibility?
Unfortunately, Swift enums can’t export to Objective-C. So what if you want to
use these awesome enum errors in Swift, but still need your code to be
consumable from Objective-C? In that case, you need to provide a way to
translate the enum error to an NSError object, and then export the NSError
object to Objective-C.
You can pull this off by adding a foundationError property to your enum error.
enumAPIError:Printable{caseNoInternetcaseHTTPError(statusCode:Int)caseServerError(message:String)privatevardomain:String{return"com.company.app"}privatevarerrorCode:Int{switchself{case.NoInternet:return100case.HTTPError(letstatusCode):return101case.ServerError(letmessage):return102}}vardescription:String{switchself{case.NoInternet:return"There is no internet connection."case.HTTPError(letstatusCode):return"The call failed with HTTP code \(statusCode)."case.ServerError(letmessage):return"The server responded with message \"\(message)\"."}}varfoundationError:NSError{returnNSError(domain:domain,code:errorCode,userInfo:[NSLocalizedDescriptionKey:description])}}
Then, in code that can return an error, you must provide overloads that return
an NSError.
The NSError-returning overload will be the only version made available to
Objective-C, so it’ll be as if your enum error code doesn’t even exist there.
Since both overloads will be visible from Swift, however, you’ll need to be
explicit about the overload you call, instead of letting Swift infer the type.
leterror:APIError?=makeCall()
It’s not the best solution, since you have to add extra code to handle codes
and domains to the error enum, and you have to use explicit typing when calling
from Swift, but it still more or less gets you the best of both worlds.
Of course, the better long-term solution to this problem is to stop using
Objective-C altogether and just use Swift full-time :D