Donald Hays' Blog2023-04-02T19:49:55-07:00Donald Haysurn:uuid:45edfb53-0d09-4722-a87f-6c7a3143e769Updated Snake and Bubble Factory for Game Boy2023-04-02T12:49:00-07:00urn:uuid:b6eaf8c6-08dd-45f9-bc35-421c240bfb79<p>It feels odd, releasing version updates for Game Boy games years after their
initial release, and decades after the system was discontinued, and yet, here
we are!</p>
<p>I’ve made updates to both my <a href="/projects/snake/">Snake</a> and
<a href="/projects/bubble-factory/">Bubble Factory</a> games.</p>
<h2 id="bubble-factory">Bubble Factory</h2>
<ul>
<li>An alert bubble is now shown next to open doors when guards are about to
appear</li>
<li>Difficulty screen now shows outlines of unearned stars to hint at the high
score challenge</li>
<li>Other minor graphical improvements</li>
<li>Removed Twitter handle on title screen</li>
</ul>
<h2 id="snake">Snake</h2>
<ul>
<li>Created a new title screen logo</li>
<li>Removed Twitter handle on title screen</li>
</ul>
Updated Bubble Factory for Game Boy2021-04-22T09:20:09-07:00urn:uuid:8cb4dba3-b9d0-4029-8006-6b34a86abedd<p>Similar to my <a href="/2021/03/09/updated-snake-for-game-boy/">previous update to Snake</a>,
I have updated <a href="/projects/bubble-factory/">Bubble Factory for Game Boy</a> to
version 1.1. This update fixes the same issue, where some emulators and flash
carts wouldn’t save high scores correctly, because the game was configured as
a non-standard cartridge type.</p>
<p>Also, like with Snake, I took the opportunity to refine the build process to be
somewhat more standard. Building the game now expects SDCC to be in your PATH,
instead of requiring you to place a copy of it in a specific location relative
to the project directory.</p>
<p>Finally, there was a best-practice change, where I now wipe sprite object
attribute memory before enabling sprites for the first time. Failing to do so
can result in an issue where corrupted, random sprites appear on screen. I’ve
never noticed this issue actually manifest in this game, but it was trivial to
do the right thing, so I did.</p>
Updated Snake for Game Boy2021-03-09T10:50:34-08:00urn:uuid:df9e23aa-4ba8-4724-b71b-3a74e4d88f9f<p>It is with great embarrassment that I’m pleased to announce version 1.1 of my
<a href="/projects/snake/">Snake game for Game Boy</a>!</p>
<p>This release fixes a single issue: high scores wouldn’t save correctly on some
emulators and flash carts.</p>
<p>Originally, the game was configured as a ROM+RAM+Battery cart type. As it turns
out, there was—as I understand—never a commercial release of a game with that
cartridge type, which means emulators don’t have a reference for how exactly it
should behave. As a result, even though every emulator I tested it on at the
time worked, I was unknowingly relying on unspecified behavior, and it turns out
that it doesn’t work everywhere. I have since changed the game to use the MBC5
memory bank controller, which is well-documented and supported. The game is
still 32 kilobytes, and if you have a save file from an emulator that supported
the old version, that same save should still work, but saving will now work on
more emulators and flash carts.</p>
<p>Also, I moved the initial stack pointer from the end of high RAM to the end of
regular RAM. This doesn’t make a practical difference for this game, but it’s
just better practice in general: high RAM is fast, but not in a way that the
stack can take advantage of.</p>
<p>Finally, updating the game involved a <a href="https://github.com/DonaldHays/snake-gb/commit/06154028c140ce53b0d797ab3015b49212935fec">sizable commit</a>
to address the surprising amount a project for a long-dead platform had rotted.
First, I switched from a custom build script to a more standard Makefile. The
updated build process also expects you to install RGBDS externally, rather than
have a copy of it in the project folder structure. These changes weren’t
<em>necessary</em>, but are better practice. But more importantly, RGBDS itself has
been evolving over time, and I addressed some deprecations and language changes.</p>
<p>Also embarrassing, Bubble Factory suffers the exact same save problem. I’ll be
fixing it, too, later.</p>
Playdate Art: Scale2019-12-30T12:49:35-08:00urn:uuid:90379d18-3074-4fb9-a4c3-9c88b5eb08f0<p>As anyone who follows me on <a href="https://twitter.com/dovuro">Twitter</a> has noticed,
I’ve been mildly utterly obsessed with <a href="https://play.date">Playdate</a>, an
upcoming handheld game system by <a href="https://panic.com">Panic</a>. As of the time of
writing, the device has yet to be released, but the Developer Preview program
will be beginning soon. I have spent spare time these past few months
experimenting with art and code in anticipation of the SDK, and thought I would
share my thoughts and observations over a few blog posts, in hopes that some of
it may be helpful to developers and designers. In this first post, I want to
talk about making art for Playdate, with a particular focus on <em>scale</em>, or
choosing the size to make your art tiles and sprites.</p>
<p>I would also like to eventually talk about things like techniques for 1-bit
black and white art, but since this post clocks in at about 1,800 words, I think
I’ll have to save that for another day!</p>
<p><em>Note: as of writing, I have no access to the SDK, and not only do I not have a
Playdate, I have never even seen one in person. I’ve made myself some software
tools to let me see my Playdate art at the correct scale, but you should consider my lack of
direct experience with the hardware when reading this. It’s entirely possible my
recommendations are grievously ill-informed!</em></p>
<h2 id="similar-but-so-very-very-different">Similar, but so Very, Very Different</h2>
<p>At first glance, Playdate looks very much like a Game Boy. It has a black and
white display, a d-pad, and two action buttons. But it’s also got a menu button,
a USB-C port, <em>and a crank</em>, but on first impression it looks very familiar.</p>
<div class="featured-image-container"><img class="post-image" alt="Playdate" src="https://donaldhays.com/img/posts/playdate-art/pd-hero.jpg" /><div class="featured-image-caption">Playdate</div></div>
<p>That familiarity piqued my attention before anything else. I’ve always had a
strong fondness for the Game Boy, so much so that over the past few years I’ve
begun doing homebrew Game Boy development as a hobby. A few years ago, I
released a game called <a href="https://donaldhays.com/projects/bubble-factory/">Bubble Factory</a>,
inspired by Game & Watch games and the later Game & Watch Gallery series of
Game Boy titles.</p>
<div class="featured-image-container"><img class="post-image pixelated" alt="Bubble Factory for Game Boy" src="https://donaldhays.com/img/posts/playdate-art/bf-gb.png" /><div class="featured-image-caption">Bubble Factory for Game Boy</div></div>
<p>When Panic announced Playdate, I knew I wanted to develop for it. But I also
didn’t want to wait for the SDK, so I set to work learning everything I could
about the device. I wrote a set of C APIs that provided a Playdate-like
environment, and then wrote an SDL backend for the APIs to let me run code for
that environment on my Mac, with the thinking that once I get the SDK I could
yank out the SDL backend and replace it with an actual Playdate SDK backend.</p>
<p>I’ve programmed various art and gameplay experiments since then, one of which is
a version of Bubble Factory for Playdate.</p>
<div class="featured-image-container"><img class="post-image pixelated" alt="Bubble Factory for Playdate" src="https://donaldhays.com/img/posts/playdate-art/bf-pd.png" /><div class="featured-image-caption">Bubble Factory for Playdate</div></div>
<p>One of the first things that may stand out to you about the Playdate version
versus the Game Boy version is the size: Playdate’s screen has a 400x240
resolution, versus the Game Boy’s 160x144. If you’ve done any development for
old systems, or if you’ve designed retro-style pixel art games, this resolution
may at first feel like it gives you a huge canvas to work with, but it’s
important to keep things in perspective. Even though Playdate’s resolution is
really high, the display’s physical size is somewhat comparable to the Game Boy,
meaning the device has a high pixels-per-inch ratio. In fact,
even though Playdate’s display is wider than the Game Boy’s, and slightly larger
across the diagonal, it’s actually a little <em>shorter</em> vertically.</p>
<div class="featured-image-container"><img class="post-image" alt="Playdate Compared to Game Boy" src="https://donaldhays.com/img/posts/playdate-art/hardware-scale.png" /><div class="featured-image-caption">Playdate Compared to Game Boy</div></div>
<p>This is why I wanted to make this post. If you design for Playdate at the scale you
would for retro systems or retro-style games, you may accidentally make art that’s
uncomfortably small when viewed on the screen, so I want to offer some
observations and suggestions.</p>
<h2 id="16-pixel-sprites-8-pixel-fonts">16 Pixel Sprites, 8 Pixel Fonts</h2>
<p>Graphics processing hardware has long been a feature of game systems, but before
3D acceleration took off we had 2D sprite hardware. Sprite processors were
capable of blitting small 2D images to a screen significantly faster than CPUs
would otherwise be capable of. Often, these systems would be built around 8x8
pixel tile images, which would be composed together to form the screen image.
Tiles could be arranged in a grid to form the background map, and tiles with
transparent cutouts could be layered on top for sprites. 8x8 pixel images were
rather small, so many games would use 2x2 arrangements of these tiles together
to form a base 16x16 pixel size for tile maps and sprites. These 2x2 tile
arrangements were called <em>metatiles</em>, but I will confusingly just refer to them
as <em>tiles</em> from here on out, just understand that on systems like the NES, 16x16 pixel
“tiles” were actually 2x2 arrangements of 8x8 pixel native tiles.</p>
<div class="featured-image-container"><img class="post-image pixelated" alt="The Legend of Zelda" src="https://donaldhays.com/img/posts/playdate-art/zelda-sizes.png" /><div class="featured-image-caption">The Legend of Zelda</div></div>
<p><em>Many</em> games were built with 16x16 pixel sprites and map tiles. On the NES, with
a resolution of 256x240, a background map made of 16 pixel tiles would give you
a 16x15 tile viewable field size.</p>
<p>The Game Boy also had 8x8 pixel hardware tiles, and so 16x16 pixel
map tiles and sprites continued to commonly appear, despite the Game Boy’s lower
screen resolution of 160x144.</p>
<div class="featured-image-container"><img class="post-image pixelated" alt="Pokémon" src="https://donaldhays.com/img/posts/playdate-art/pokemon-sizes.png" /><div class="featured-image-caption">Pokémon</div></div>
<p>While sprites and map tiles were commonly 16x16 pixels, it was also common to
make fonts fit within 8x8 pixel tiles. Usually, these fonts would have a cap
height of 7 pixels, with 1 pixel remaining on the bottom for descenders.
Examples of these are highlighted on both of the earlier screenshots.</p>
<p>Because these sizes were so common, they continue to be strongly associated with
retro games today. <a href="https://yachtclubgames.com/shovel-knight/">Shovel Knight</a>,
for example, is a modern retro game, and while most sprites are larger than 16
pixels, it continues to use an 8-pixel monospace font and has maps made of 16
pixel tiles.</p>
<p>Since Playdate is so evocative of older systems, then, it may be tempting to use
similar sizes when designing for it. But I feel that would likely be a mistake.
Because the pixel density is so high, an 8-pixel font or 16-pixel character will
look <em>very</em> small on the screen.</p>
<div class="featured-image-container"><img class="post-image" alt="Playdate Sprite Sizes to Scale Against Game Boy" src="https://donaldhays.com/img/posts/playdate-art/sprite-scale.png" /><div class="featured-image-caption">Playdate Sprite Sizes to Scale Against Game Boy</div></div>
<p>In fact, art of any given size will appear about half as big as it would on
the Game Boy. Because of that, consider drawing larger assets than you might
otherwise if you were designing a retro-style game. A 24-pixel character on
Playdate will appear slightly smaller than a 16-pixel character on the Game Boy,
and a 32-pixel character on Playdate will be comparable to the Game Boy’s 16.</p>
<p>If you have experience designing for smartphones, making Playdate art versus art
for retro systems feels almost like designing assets for a “Retina” display as
compared to a “1x” display.</p>
<div class="featured-image-container"><img class="post-image" alt="Playdate Font Sizes to Scale Against Game Boy" src="https://donaldhays.com/img/posts/playdate-art/font-scale.png" /><div class="featured-image-caption">Playdate Font Sizes to Scale Against Game Boy</div></div>
<p>This scaling advice applies with particular emphasis for fonts. Sharp though the
display may be, an 8-pixel (or smaller) font will appear <em>very</em> small, and may
be difficult to read, especially for body text. I previously tweeted out an
earlier mockup image of Bubble Factory, and
<a href="https://twitter.com/mrgan">Neven Mrgan</a> at Panic
<a href="https://twitter.com/mrgan/status/1144014542478794752">uploaded the image to a device</a>.
But the font for “Score”, “High Score”, and “Miss” was smaller than it is today,
and <a href="https://twitter.com/AlexGuichet">Alex Guichet</a> observed that it may be too
small, which Neven confirmed to be the case.</p>
<p>Now, the font today is larger, but still pretty small 😬, so here I go ignoring
my own advice, but I feel a reasonable exception exists for minor text elements.
If you look at the clock in <a href="https://twitter.com/playdate/status/1131307589587701760">Crankin’s Time Travel Adventure</a>,
you’ll see that the “PM” font is far smaller than 8 pixels, but the time font is
much larger. Important text, including dialog, scores, and health, should lean
towards larger fonts.</p>
<h2 id="tile-size-for-grid-based-games">Tile Size for Grid-Based Games</h2>
<p>Because Playdate uses a CPU-based bitmap renderer instead of dedicated sprite
hardware <em>(note: I suppose I could be wrong on that, but as I understand it’s
the case)</em>, you’re not really boxed into grid-based games with tiles that are a
multiple of 8 pixels in size—Crankin’s Time Travel Adventure certainly isn’t—but if you
make a game based on a grid, you need to choose a base tile size.</p>
<p>I’m going to refer to the number of tiles on screen as the <em>field size</em>. The
field size has an inverse relationship to the tile size. The larger your tiles,
the fewer of them you can fit on screen. For example, a hypothetical 100-pixel
screen could fit 4 tiles if they’re 25 pixels each, but 5 tiles if they’re 20
pixels.</p>
<p>Field size is also independent from map size, which is the overall horizontal
and vertical number of tiles in a level. Field size refers to the subset of a
map that can be viewed on screen in a given moment. If the map size is larger
than the field size, then scrolling will be necessary to view the whole map.</p>
<p>Smaller fields tend to feel more cramped, because you can’t see as many tiles
away from your character, but larger fields require smaller tiles, which can be
harder on the eyes. Therefore, you need to strike a balance between the two.</p>
<div class="featured-image-container"><img class="post-image" alt="Viewable Field Areas for Different Resolutions and Tile Sizes" src="https://donaldhays.com/img/posts/playdate-art/field-sizes.png" /><div class="featured-image-caption">Viewable Field Areas for Different Resolutions and Tile Sizes</div></div>
<p>With 16 pixel tiles, the NES has a 16x15 field size, and the Game Boy has a
10x9. If you played Mega Man games on both the NES and the Game Boy, you may
recall that the Game Boy games tended to feel more cramped, because sprites
and map tiles were the same size as on the NES, but the field size was
smaller on the Game Boy.</p>
<p>Since Playdate’s screen size is comparable to the Game Boy (especially along the
vertical axis), you may want to pick a tile size that gives you a
comparable field size. If your tiles are 24 pixels, your field would be 10 tiles
tall, while 32 pixels would give you a field 7.5 tiles tall. You can also choose
smaller or larger tiles, with the natural tradeoffs for viewing comfort versus field size.</p>
<div class="featured-image-container"><img class="post-image pixelated" alt="Chess" src="https://donaldhays.com/img/posts/playdate-art/chess.png" /><div class="featured-image-caption">Chess</div></div>
<p>You may also want to consider <em>non-square</em> tiles. When I designed a Chess set,
I made 24 and 32-pixel variants. I really liked the 32-pixel pieces, but a Chess
board is 8 squares tall, and so it wouldn’t fit a 7.5 tile field. But by
squishing the y-axis and letting the pieces overlap the squares above them, I
got the whole board (and then some) to fit while holding 32-pixel pieces, and it
gives the board a nice perspective feel, too. Be creative!</p>
<p>I spent much of this blog post directly comparing the Game Boy to Playdate, but
I want to emphasize that Playdate is very much its own thing, so don’t feel
constrained by old sizing conventions. Do what feels right for your game, just
be aware that the screen has a high pixel density, so you should consider
whether you’re designing something that’ll appear too small.</p>
Announcing Bubble Factory for Game Boy2017-06-30T12:39:43-07:00urn:uuid:fa79ca9d-0c86-49ef-a624-a0cbdd4e4126<p>Is it weird to release a new Game Boy game in 2017? It’s not a huge game, but
I’m releasing a new game called <a href="/projects/bubble-factory/">Bubble Factory</a>!</p>
<p>This follows the <a href="/projects/snake/">Snake</a> game I made last year. That game was
written in Z80 assembly, while this game was written almost entirely in C.</p>
<p>It was really educational to write a game in assembly, but I found C to be
(unsurprisingly) more productive. I think I’ll probably stick with C if I write
any more Game Boy games.</p>
<p>The <a href="https://github.com/DonaldHays/bubblefactory">source</a> is available if you’re
interested in seeing how it works.</p>
Added a JSON Feed2017-06-01T02:22:59-07:00urn:uuid:387d349d-ae07-4563-a5f3-f3cc1d7843a8<p>It’s been a while since my last post, and I only have a brief update today. I
added support for <a href="https://jsonfeed.org">JSON Feed</a> to this site. You can view
it <a href="https://donaldhays.com/feed.json">here</a>. It joins the existing
<a href="https://donaldhays.com/atom.xml">Atom feed</a>.</p>
Minor Sunset Controls Update2016-01-16T06:54:59-08:00urn:uuid:d66af24b-e73d-4169-9946-c1c2e342ee06<p>I made a minor update to <a href="https://donaldhays.com/projects/sunset/">Sunset</a> today.</p>
<p>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!</p>
<p>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!</p>
Uploaded Sunset Again2016-01-14T12:54:09-08:00urn:uuid:6ffee588-0e36-4067-9635-24816c7b53a9<p>I just re-uploaded <a href="https://donaldhays.com/projects/sunset/">Sunset</a> to my site.</p>
<p>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.</p>
<p>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.</p>
<p>Much has changed since then. WebGL is a thing, enabling browser games to <a href="http://www.playkeepout.com">far exceed</a>
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
<a href="https://donaldhays.com/projects/sunset/battle.html">stair-stepping</a> 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!</p>
<p>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.</p>
<p>So if you want to play a small, free game that might keep you busy for a few
hours, give it a try!</p>
Maidenhead Converter 2.2.0/2.2.1 and the Importance of QA2015-08-29T03:41:29-07:00urn:uuid:1752c69a-2251-4b97-bba1-aa3007a02fb2<p><a href="https://www.maidenheadapp.com/">Maidenhead Converter</a> 2.2.0 went live about a
week ago, and—due to a bug—2.2.1 followed soon after.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">SKProductsRequest</span><span class="p">(</span><span class="nv">productIdentifiers</span><span class="p">:</span> <span class="kt">NSSet</span><span class="p">(</span><span class="nv">array</span><span class="p">:</span> <span class="n">identifiers</span><span class="p">))</span></code></pre></figure>
<p>to:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">SKProductsRequest</span><span class="p">(</span><span class="nv">productIdentifiers</span><span class="p">:</span> <span class="kt">Set</span><span class="o"><</span><span class="kt">NSObject</span><span class="o">></span><span class="p">(</span><span class="nv">arrayLiteral</span><span class="p">:</span> <span class="n">identifiers</span><span class="p">))</span></code></pre></figure>
<p>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:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">SKProductsRequest</span><span class="p">(</span><span class="nv">productIdentifiers</span><span class="p">:</span> <span class="kt">Set</span><span class="p">(</span><span class="n">identifiers</span><span class="p">))</span></code></pre></figure>
<p>Which <em>also</em> 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.</p>
<p>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.</p>
Maidenhead Converter 2.1.12015-02-03T10:50:55-08:00urn:uuid:d97a8a19-2b28-4d92-ac26-b5f13d45ec10<p><a href="https://www.maidenheadapp.com/">Maidenhead Converter</a> 2.1.1 is now live. This
was largely a refactor and bugfix release, there aren’t any new features.</p>
<p>I like using Maidenhead Converter as a testing ground for ideas, patterns and
practices that I apply to my work on <a href="https://www.eventboard.io">EventBoard</a>.
Maidenhead Converter is small and simple, but does a real task.</p>
<p>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.</p>
<p>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.</p>
<p>What I <em>do</em> 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.</p>
<p>Now that this release is done, I intend to do 2.2.0, which will include some
new features.</p>
Enum Errors in Swift2014-12-16T13:01:52-08:00urn:uuid:9ee40e0a-3f33-4e4b-82cd-95b52216c116<p><a href="https://twitter.com/bradlarson">Brad Larson</a> of
<a href="http://www.sunsetlakesoftware.com">Sunset Lake Software</a> wrote a
<a href="http://www.sunsetlakesoftware.com/2014/12/02/why-were-rewriting-our-robotics-software-swift">blog post</a>
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.</p>
<p>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 <a href="https://groups.google.com/forum/#!msg/llamakit/cQdv2i2A4Zw/FYP10NuRYOwJ">mailing list post</a>.
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.</p>
<h2 id="whats-painful-about-nserror">What’s Painful About NSError</h2>
<p>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 <a href="https://nshipster.com/nserror/">NSHipster</a>.</p>
<p>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.</p>
<p>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.</p>
<p>Using Swift enums nicely addresses these complaints.</p>
<h2 id="a-swifty-enum-error">A Swifty Enum Error</h2>
<p>So what does an enum error look like? Here’s an example of a hypothetical error
type for interacting with an API server.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">APIError</span> <span class="p">{</span>
<span class="k">case</span> <span class="kt">NoInternet</span>
<span class="k">case</span> <span class="kt">HTTPError</span><span class="p">(</span><span class="nv">statusCode</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span>
<span class="k">case</span> <span class="kt">ServerError</span><span class="p">(</span><span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>A function which can produce an error could look like this.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">typealias</span> <span class="kt">APICallback</span> <span class="o">=</span> <span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="kt">NSData</span><span class="p">?,</span> <span class="nv">error</span><span class="p">:</span> <span class="kt">APIError</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span>
<span class="kd">func</span> <span class="nf">makeAPICall</span><span class="p">(</span><span class="nv">callback</span><span class="p">:</span> <span class="kt">APICallback</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Void</span> <span class="p">{</span>
<span class="c1">// Success</span>
<span class="nf">callback</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="kt">NSData</span><span class="p">(),</span> <span class="nv">error</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="c1">// There was no internet connection</span>
<span class="nf">callback</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">error</span><span class="p">:</span> <span class="o">.</span><span class="kt">NoInternet</span><span class="p">)</span>
<span class="c1">// There was an HTTP error code</span>
<span class="nf">callback</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">error</span><span class="p">:</span> <span class="o">.</span><span class="kt">HTTPError</span><span class="p">(</span><span class="nv">statusCode</span><span class="p">:</span> <span class="mi">404</span><span class="p">))</span>
<span class="c1">// The server rejected the call</span>
<span class="nf">callback</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">error</span><span class="p">:</span> <span class="o">.</span><span class="kt">ServerError</span><span class="p">(</span><span class="nv">message</span><span class="p">:</span> <span class="n">errorMessage</span><span class="p">))</span>
<span class="p">}</span></code></pre></figure>
<p>Creating errors becomes easy, clear, and descriptive. It’s even
statically-typed, so the compiler will require you to construct the error
correctly.</p>
<p>Consuming errors is simple, as well. You could use a switch statement to handle
some—or all—cases.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">switch</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">NoInternet</span><span class="p">:</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"You should get a better ISP"</span><span class="p">)</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">ServerError</span><span class="p">(</span><span class="k">let</span> <span class="nv">message</span><span class="p">):</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"The server responded with message: </span><span class="se">\(</span><span class="n">message</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="k">default</span><span class="p">:</span>
<span class="c1">// Handle other error cases in a general way</span>
<span class="p">}</span></code></pre></figure>
<p>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.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">APIError</span><span class="p">:</span> <span class="kt">Printable</span> <span class="p">{</span>
<span class="k">case</span> <span class="kt">NoInternet</span>
<span class="k">case</span> <span class="kt">HTTPError</span><span class="p">(</span><span class="nv">statusCode</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span>
<span class="k">case</span> <span class="kt">ServerError</span><span class="p">(</span><span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span>
<span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">NoInternet</span><span class="p">:</span>
<span class="k">return</span> <span class="s">"There is no internet connection."</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">HTTPError</span><span class="p">(</span><span class="k">let</span> <span class="nv">statusCode</span><span class="p">):</span>
<span class="k">return</span> <span class="s">"The call failed with HTTP code </span><span class="se">\(</span><span class="n">statusCode</span><span class="se">)</span><span class="s">."</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">ServerError</span><span class="p">(</span><span class="k">let</span> <span class="nv">message</span><span class="p">):</span>
<span class="k">return</span> <span class="s">"The server responded with message </span><span class="se">\"\(</span><span class="n">message</span><span class="se">)\"</span><span class="s">."</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Printing the error then becomes as simple as this.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="nf">println</span><span class="p">(</span><span class="n">error</span><span class="p">)</span></code></pre></figure>
<p>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.</p>
<h2 id="what-about-objective-c-compatibility">What About Objective-C Compatibility?</h2>
<p>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.</p>
<p>You can pull this off by adding a foundationError property to your enum error.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">APIError</span><span class="p">:</span> <span class="kt">Printable</span> <span class="p">{</span>
<span class="k">case</span> <span class="kt">NoInternet</span>
<span class="k">case</span> <span class="kt">HTTPError</span><span class="p">(</span><span class="nv">statusCode</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span>
<span class="k">case</span> <span class="kt">ServerError</span><span class="p">(</span><span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">domain</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"com.company.app"</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">errorCode</span><span class="p">:</span> <span class="kt">Int</span> <span class="p">{</span>
<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">NoInternet</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">100</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">HTTPError</span><span class="p">(</span><span class="k">let</span> <span class="nv">statusCode</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">101</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">ServerError</span><span class="p">(</span><span class="k">let</span> <span class="nv">message</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">102</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">NoInternet</span><span class="p">:</span>
<span class="k">return</span> <span class="s">"There is no internet connection."</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">HTTPError</span><span class="p">(</span><span class="k">let</span> <span class="nv">statusCode</span><span class="p">):</span>
<span class="k">return</span> <span class="s">"The call failed with HTTP code </span><span class="se">\(</span><span class="n">statusCode</span><span class="se">)</span><span class="s">."</span>
<span class="k">case</span> <span class="o">.</span><span class="kt">ServerError</span><span class="p">(</span><span class="k">let</span> <span class="nv">message</span><span class="p">):</span>
<span class="k">return</span> <span class="s">"The server responded with message </span><span class="se">\"\(</span><span class="n">message</span><span class="se">)\"</span><span class="s">."</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">var</span> <span class="nv">foundationError</span><span class="p">:</span> <span class="kt">NSError</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">NSError</span><span class="p">(</span><span class="nv">domain</span><span class="p">:</span> <span class="n">domain</span><span class="p">,</span> <span class="nv">code</span><span class="p">:</span> <span class="n">errorCode</span><span class="p">,</span> <span class="nv">userInfo</span><span class="p">:</span> <span class="p">[</span>
<span class="kt">NSLocalizedDescriptionKey</span> <span class="p">:</span> <span class="n">description</span>
<span class="p">])</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Then, in code that can return an error, you must provide overloads that return
an NSError.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">makeCall</span><span class="p">()</span> <span class="o">-></span> <span class="kt">APIError</span><span class="p">?</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">.</span><span class="kt">NoInternet</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">makeCall</span><span class="p">()</span> <span class="o">-></span> <span class="kt">NSError</span><span class="p">?</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">makeCall</span><span class="p">()?</span><span class="o">.</span><span class="n">foundationError</span>
<span class="p">}</span></code></pre></figure>
<p>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.</p>
<p>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.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">error</span><span class="p">:</span> <span class="kt">APIError</span><span class="p">?</span> <span class="o">=</span> <span class="nf">makeCall</span><span class="p">()</span></code></pre></figure>
<p>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.</p>
<p>Of course, the better long-term solution to this problem is to stop using
Objective-C altogether and just use Swift full-time :D</p>
Benefits of Swift Embedded Frameworks in iOS 82014-10-30T17:46:35-07:00urn:uuid:99c1926e-2f03-4065-9a9f-21a7dce7643e<p>Since WWDC, I’ve spent a lot of time playing with <a href="https://www.apple.com/swift/">Swift</a>.
I’ve even shipped two apps written in the language:
<a href="https://itunes.apple.com/us/app/eventboard-it-manage-your/id924086883?mt=8">EventBoard IT</a>
at work and <a href="https://www.maidenheadapp.com/">Maidenhead Converter</a> on my own
time.</p>
<p>Overall, I quite like the language, but it’s clearly in an early state. I would
like to talk about Swift in greater detail later, but for now I want to focus
on one particular issue I’ve had that I only just recently conquered, and some
surprising additional benefits that the solution provided.</p>
<h2 id="compile-times">Compile Times</h2>
<p>The Swift compiler is <em>slow</em>. On my MacBook Pro, every 100 lines of code
adds a full second to the compile time. EventBoard IT weighs in at about 4,000
lines of code, and takes 42 seconds to compile. When the same machine can
compile a 30,000 line Objective-C app in a fraction of the time, that’s
unacceptably slow. My much faster iMac compiles EventBoard IT in a third the
time, which is better, but still too slow.</p>
<p>This one issue was leading me to postpone doing further significant work in the
language, which saddened me. I tried numerous suggestions to improve
compile times—explicitly type everything instead of taking advantage of
the implicit typing feature, watch the build log to see if any one file was
taking a disproportionately long time, etc—but nothing seemed to make a
difference.</p>
<p>While the compiler needs to speed up generally, one of the biggest
problems it has right now is that it doesn’t <em>delta compile</em>; every time you hit
build it will recompile the entire app, and not just the parts that changed
since the last build.</p>
<p>The Swift team explained that this is because of the nature of the language.
In Objective-C you have explicit <code class="language-plaintext highlighter-rouge">#include</code> statements that make it really easy
to build a dependency graph, which makes it possible to see which parts of an
app might be affected by a change in one source file. Swift doesn’t have that:
any source file in a target can see any symbol in any other source file in the
same target. Because of that, the compiler can’t easily judge which files might
be affected by a change in another file. The Swift team has said they intend to
implement dependency analysis in the same way the <a href="https://en.wikipedia.org/wiki/C_Sharp_(programming_language)">C#</a>
team does, but that doesn’t exist yet, so your entire app recompiles every
build.</p>
<p>As it turns out, there’s a way you can take advantage of delta compiles in Swift
today: embedded frameworks. While the compiler can’t do dependency analysis
<em>inside</em> a target, it can do it <em>between</em> targets. If the code in an
embedded framework didn’t change since a previous compile, it doesn’t need to be
recompiled.</p>
<p>A few days ago I started pushing parts of EventBoard IT out into embedded
frameworks. I’m not done with the process yet, but I’ve already cut the compile
time nearly in half: a delta compile takes 24 seconds. Interestingly, a full
compile only takes 30 seconds now. I suspect that’s because of a combinatorial
explosion effect, where every file you add to your project adds to the compile
time of every other file in your project. Since the project is being split into
different modules, each module has less to consider than the app as a whole did.</p>
<p>This fix makes me excited to use Swift <em>today</em> again. But as it turns out,
that’s not the end of the benefits that embedded frameworks yield.</p>
<h2 id="public-header-view">Public Header View</h2>
<p>Header files in languages like Objective-C have one really distinct advantage:
you can declare and document your public interface in the header file, while
hiding all of the implementation in the implementation file. When done well,
you can look at a header file to learn how to use a class,
without ever having to look at the implementation. Modern languages don’t have
header files, and so everything goes in the implementation file. This is
unfortunate, but not so unfortunate that I think header files have any place
in a modern language. Swift made the right decision ditching them.</p>
<p>But wouldn’t it be nice if you still had a view in Xcode that just showed you
the public interface of your code, with its documentation comments? You get this
view when you inspect symbols on any built-in framework like UIKit, but what
about your own code?</p>
<p>Embedded frameworks give you this public header view. In source files that use
your framework, you include an <code class="language-plaintext highlighter-rouge">import</code> statement. If you command-click the
framework name in the <code class="language-plaintext highlighter-rouge">import</code> statement, you will see the public header view.
It shows the documentation comments you wrote and the public symbol
declarations, but not your private nor internal symbols, and not the
implementations of your public symbols. It’s glorious.</p>
<h2 id="limited-scope">Limited Scope</h2>
<p>When you write a view controller, you often write various sub-controllers or
views or model objects that are used by the view controller, but not by anything
else in the app. Your classes might also have public properties and methods
that are used by these near-neighbor classes, but are not relevant to the rest
of the app. Unfortunately, even though these classes are only used by the one
view controller, their symbols are visible to the rest of the app. Which means
that anything else in your app could instantiate or manipulate them.</p>
<p>In my experience, one of the best ways to improve robustness in your software is
to limit the scope your code has visibility of at any given moment. Minimize
global variables, use <code class="language-plaintext highlighter-rouge">const</code> wherever you can, etc. The fewer moving parts your
code has access to at any given point, the fewer moving parts it can potentially
break or depend on in brittle ways.</p>
<p>Classes are, in a sense, themselves global variables. <code class="language-plaintext highlighter-rouge">NSObject</code> can be thought
of as a global variable that refers to an object that can be used to construct
instances of <code class="language-plaintext highlighter-rouge">NSObject</code>. Every class you declare adds another
object-constructing global variable to the scope of the rest of your app. A bit
unnerving to think of it that way, no? But what can you do? Either hide all of
your view controller’s related classes in the same implementation file, or
accept that you’re exposing those symbols to the rest of your app. It’s
workable, but it kind of sucks.</p>
<p>Swift’s access control support combined with embedded frameworks improves this
situation. By default, symbols in a Swift framework are <code class="language-plaintext highlighter-rouge">internal</code>, which means
they can be seen by anything within the target, but nothing outside it. In an
embedded framework, you have to explicitly annotate symbols with <code class="language-plaintext highlighter-rouge">public</code> to
make them visible outside the target. So you can make a view controller with a
host of utility classes and only annotate the view controller itself as
<code class="language-plaintext highlighter-rouge">public</code>, and then the rest of your classes have no visibility to the rest of
the app. Boom, those classes are no longer global variables—at least, not
outside their framework.</p>
<p>A related benefit: you’re forced to think more explicitly about exactly which
symbols should be public. You ask yourself if something really needs to be
public, or which module something really belongs in. It helps push you towards
clean interfaces.</p>
<h2 id="im-loving-embedded-frameworks">I’m Loving Embedded Frameworks</h2>
<p>It feels like these new embedded frameworks in iOS 8 are going to result in a
significant change in the way I build apps. I’m going to try to continually silo
things off in their own framework wherever I can. The compile time complaint
will (hopefully) go away with improvements to the compiler, but the public
header view and especially the improved ability to limit scope are really
substantial benefits.</p>
Maidenhead Converter 2.02014-09-30T14:33:28-07:00urn:uuid:53155576-06bb-4709-ad20-b3bdfe18e486<p>Tonight, I’m bringing <a href="https://maidenheadapp.com">Maidenhead Converter</a> back to
the App Store.</p>
<p>I pulled all my apps off the App Store some time ago (was it last year? Or
the year before? I forget.) I would like to start getting improved versions of
them back up on the store, beginning with Maidenhead Converter.</p>
<p>2.0 is a complete rewrite and redesign of the app. A lot has changed since 2009,
including a new OS design in iOS 7 and new 4, 4.7, and 5.5 inch iPhones. Also
the iPad happened, but I’m not quite optimizing for that just yet.</p>
<p>I had a number of feature requests for the original app that I never
implemented. Folks wanted different coordinate formats—added!—a map
<em>in</em> the app—added!—and the ability to get the heading between two points—not
added yet, but give me time! I would also like to add features like a Maidenhead
grid overlay on top of the app.</p>
<p>Technologically, I wanted to experiment with Swift and Storyboards. I’ve also
used Swift in a larger app for work, and I would like to write my thoughts on
the language in detail later. Long story short: I like it, but the compiler is
really not ready for prime time yet. Storyboards worked out fine, but it was a
small app and I’m not yet entirely sold on the idea of using Storyboards with a
large app. Maybe if you split into multiple Storyboards. I need to think on it
and experiment more.</p>
<p>When Maidenhead Converter was a paid app, it never made much money. After I set
it free, it got plenty of downloads, but obviously stopped making money. This
time around I’ve decided to experiment with iAd. The app now shows banner ads
on the main screen. If you dislike the ads, you can purchase a $1.99 ad disabler
to turn them off. I don’t expect to make any serious money on the app, but maybe
it’ll earn enough for some nice food every now and then.</p>
<p>Maidenhead Converter 2.0 requires iOS 8. <a href="https://itunes.apple.com/us/app/maidenhead-converter/id313870236?ls=1&mt=8">Download
it</a>
now!</p>
4 Years, 5 Months, 4 Days2014-09-14T03:26:51-07:00urn:uuid:bbd0543c-151f-4ce1-92b9-af2f9e406265<p>On April 10, 2010, I put my site into a dormant state. I haven’t written a blog
post since.</p>
<p>Every few months, I would try rewriting my site, but then lose interest and put
the project down again. The last live version of my site was written in
<a href="https://php.net">PHP</a> and hosted on <a href="https://www.godaddy.com">GoDaddy</a>. My first
rewrites were in PHP on <a href="https://www.webfaction.com">WebFaction</a> (which I
recommend over GoDaddy). Then I experimented with <a href="https://nodejs.org">Node.js</a>
(which I recommend over PHP) on <a href="https://azure.microsoft.com">Azure</a>. Now
finally, what you see is a static site generated by
<a href="https://jekyllrb.com">Jekyll</a> and hosted on
<a href="https://pages.github.com">GitHub Pages</a>.</p>
<p>The workflow for writing posts in this new setup is more command-line and
git-commit centered than I would prefer, but it does come with the advantage
that I can add new CSS and images and whatever else I need in the same commit,
and have it all go live at the same time. I also frequently use my personal site
to host little experimental prototype projects, and getting those online will be
easier now.</p>
<p>There’s been much I’ve wanted to write about. I’ve been
<a href="https://twitter.com/dovuro">tweeting</a> about
<a href="https://developer.apple.com/swift/">Swift</a> an awful lot, but blog posts are a
much better place to show code than PNGs. I’ve also long wanted to write about
my approach to app architecture on iOS. On occasion I buy new hardware and want
to write a review about it. Now I can.</p>
<p>So, I’m back, and hopefully my next post won’t take four years.</p>