• Building Apple TV apps with Middleman

    At Kisko Labs we were lucky enough to receive an Apple TV developer kit from Apple (two actually, because I got one for my personal Apple developer account too). Motivated by the free hardware, I took some time to learn how to develop for the new Apple TV (one TVML app and one “native” app). In this post I’ll walkthrough creating a TVML app with Middleman.

    Apple TV (4th generation)

    What is TVML/TVMLKit?

    In Apple’s words:

    The TVMLKit framework enables you to incorporate JavaScript and TVML files in your binary apps to create client-server apps.

    In other words your Apple TV app can download JavaScript and XML files from your server (or any HTTP server, we used S3 and CloudFront for our app). The Apple TV will automatically generate the UI for you based on the XML templates your provide.

    The apps you can create with TVMLKit are limited, but it looks perfect for media heavy applications (e.g. viewing videos or photos).

    If you’ve used an older 3rd generation Apple TV, the applications created with TVML will look very familiar…

    What do I need (to know) for this tutorial?

    Passing knowledge about creating sites with Middleman will suffice (I’m going to assume that you have a recent version installed). You should also have Xcode 7.1 installed and be vaguely familiar with using it. An actual Apple TV is not required (the simulator will suffice).

    The versions I used:

    • Xcode 7.1
    • Middleman 3.4
    • Ruby 2.2

    I also assume that you know how to navigate around and run commands in a terminal environment.

    What will this tutorial cover?

    Creating a new TVML app in Xcode, creating a simple backend for it with Middleman, and using the app to play videos from the Internet Archive’s 35mm Stock Footage collection.

    0. Initial setup

    Make sure that you have Xcode 7.1 and Middleman 3.4 installed. Then create a directory for the work in this tutorial:

    mkdir StockFootageTV
    cd StockFootageTV

    1. Create the Middleman app

    Start by creating a new Middleman app called backend in the directory you just created:

    middleman init backend
    cd backend

    We’ll be using Slim to generate the XML for us, so add the following line to the Gemfile:

    gem "slim", "~> 3.0.6"

    Once this is done, run bundle install.

    Then create a new file called application.js.coffee in the source/javascripts/ directory with the following contents:

    createAlert = (title, description) ->
      alertString = """<?xml version="1.0" encoding="UTF-8" ?>
      parser = new DOMParser
      alertDoc = parser.parseFromString(alertString, "application/xml")
    App.onLaunch = (options) ->
      doc = createAlert("Hello World", "This is a TVMLKit app")

    We’ll use this as the basis for our TVMLKit app. Next let’s set-up the project in Xcode. You can delete the existing all.js file if you wish, we won’t need it.

    2. Create the tvOS app

    Open Xcode and select Create a new Xcode project from the start screen (or File → New → Project from the menu).

    Create a new Xcode project

    In the next screen, choose to create a Single View Application for tvOS.

    Choose a template for your new application

    Set the product name to StockFootageTV and the language to Swift.

    Choose options for your new project

    Finally, save the project to the StockFootageTV directory you created in Step 0.

    At the end of this step, you should have a directory structure that looks something like this (with a lot more files):

    ├── StockFootageTV
    │   ├── StockFootageTV
    │   ├── StockFootageTV.xcodeproj
    │   ├── StockFootageTVTests
    │   └── StockFootageTVUITests
    └── backend
        ├── Gemfile
        ├── Gemfile.lock
        ├── config.rb
        └── source

    You can test that the Xcode project is working by hitting ⌘ + R in Xcode (you should get an Apple TV simulator with a blank screen).

    3. Do some tedious set-up work in Xcode

    Before we can crack on with our awesome TVML app, we need to do some chores in Xcode.

    First of all, delete the ViewController.swift and Main.storyboard files from the project (select Move to Trash when Xcode asks).

    Then select the project in the left sidebar, choose the General tab, and clear the Main Interface box:

    Main Interface

    Finally, open the Info.plist file from the left sidebar, right click and click Add Row and paste in NSAppTransportSecurity. It should automatically expand to “App Transport Security Settings” and the Type should be set to Dictionary.

    Click on the arrow to expand the entry and add a entry with the key NSAllowsArbitraryLoads (or “Allow Arbitrary Loads” in English). Set the value to YES.

    Info.plist edits

    4. Set up the tvOS app to load application.js

    First of all, start the Middleman development server:

    bundle exec middleman server

    Go to http://localhost:4567/javascripts/application.js to check that the JavaScript actually loads correctly.

    Then open AppDelegate.swift in Xcode and replace its contents with the following (you can leave the comments at the top of the file as they are):

    import UIKit
    import TVMLKit
    class AppDelegate: UIResponder, UIApplicationDelegate, TVApplicationControllerDelegate {
        var window: UIWindow?
        var appController: TVApplicationController?
        let baseURL = "http://localhost:4567"
        func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            window = UIWindow(frame: UIScreen.mainScreen().bounds)
            let context = TVApplicationControllerContext()
            let javaScriptURL = NSURL(string: "\(baseURL)/javascripts/application.js")!
            context.javaScriptApplicationURL = javaScriptURL
            context.launchOptions["BASEURL"] = baseURL
            appController = TVApplicationController(context: context, window: window, delegate: self)
            return true

    Save the file and run the app again with ⌘ + R. You should be greeted with the following view in the Apple TV simulator:

    Hello World!

    Good Job! You now have a working, albeit useless, tvOS app!

    5. Generate some XML

    Since we don’t want to write out all the entries by hand, let’s create a YAML file with a list of our videos. In your Middleman app, create a new file called data/content.yml directory (create the data directory too, if you don’t have it already).

      - title: "Various videos"
          - title: "Downtown Los Angeles streets"
            description: "Downtown Los Angeles streets, process plates, color."
            url: "https://archive.org/download/PET0981_R-2_LA_color/PET0981_R-2_LA_color_720p.mp4"
            preview: "https://archive.org/download/PET0981_R-2_LA_color/PET0981_R-2_LA_color.thumbs/PET0981_R-2_LA_color_000027.jpg"
          - title: "Hot Rods, Southern California, 1940s"
            description: "Scenes at Southern California racetrack with hot rods and drivers."
            url: "https://archive.org/download/27EG-28-EG-58_HOTRODS/27EG-28-EG-58_HOTRODS_720p.mp4"
            preview: "https://archive.org/download/27EG-28-EG-58_HOTRODS/27EG-28-EG-58_HOTRODS.thumbs/27EG-28-EG-58_HOTRODS_000027.jpg"
          - title: "Internet Archive 35mm Stock Footage Sample Reel"
            description: "This reel shows samples from Internet Archive's 35mm Stock Footage collection."
            url: "https://archive.org/download/InternetArchive35mmStockFootageSampleReel/IA35mmSampleReel_720p.mp4"
            preview: "https://archive.org/download/InternetArchive35mmStockFootageSampleReel/InternetArchive35mmStockFootageSampleReel.thumbs/IA35mmSampleReel_000054.jpg"
      - title: "Comedy"
          - title: "The Mechanic (Part 1)"
            description: "Silent comedy taking place in an automobile garage."
            url: "https://archive.org/download/AFP-19-OJ_R-2_TheMechanic_A/AFP-19-OJ_R-2_TheMechanic_A_720p.mp4"
            preview: "https://archive.org/download/AFP-19-OJ_R-2_TheMechanic_A/AFP-19-OJ_R-2_TheMechanic_A.thumbs/AFP-19-OJ_R-2_TheMechanic_A_000054.jpg"
          - title: "The Mechanic (Part 2)"
            description: "Silent comedy taking place in an automobile garage. 2/2"
            url: "https://archive.org/download/AFP-19-OJ_R-2_TheMechanic_B/AFP-19-OJ_R-2_TheMechanic_B_720p.mp4"
            preview: "https://archive.org/download/AFP-19-OJ_R-2_TheMechanic_B/AFP-19-OJ_R-2_TheMechanic_B.thumbs/AFP-19-OJ_R-2_TheMechanic_B_000005.jpg"

    Then create a new file called videos.xml.slim in the source directory. Add the following contents to it:

    doctype xml
          title 35mm Stock Footage
          - data.content.video_sections.each do |video_section|
                title= video_section.title
                decorationLabel= video_section.videos.size
                      - video_section.videos.each do |video|
                        lockup videoURL=video.url
                          img src=video.preview width=500 height=308
                          title= video.title
                          description = video.description

    Then open up the config.rb file, and configure Middleman not to use layouts for XML files by adding the page "*.xml", layout: false line to the Page options, layouts, aliases and proxies section of the file.

    # ... snip ...
    # Page options, layouts, aliases and proxies
    page "*.xml", layout: false
    # ... snip ...

    Finally, open http://localhost:4567/videos.xml in your browser to ensure that the XML is being generated correctly.

    videos.xml in Safari

    If you’ve done everything correctly, the backend directory should have the following contents:

    ├── Gemfile
    ├── Gemfile.lock
    ├── config.rb
    ├── data
    │   └── content.yml
    └── source
        ├── images
        │   ├── background.png
        │   └── middleman.png
        ├── index.html.erb
        ├── javascripts
        │   └── application.js.coffee
        ├── layouts
        │   └── layout.erb
        ├── stylesheets
        │   ├── all.css
        │   └── normalize.css
        └── videos.xml.slim

    6. Load the XML from our JavaScript

    Open the application.js.coffee file again and replace it with the following:

    createAlert = (title, description) ->
      alertString = """<?xml version="1.0" encoding="UTF-8" ?>
      parser = new DOMParser
      alertDoc = parser.parseFromString(alertString, "application/xml")
    readBody = (xhr) ->
      data = undefined
      if !xhr.responseType or xhr.responseType == 'text'
        data = xhr.responseText
      else if xhr.responseType == 'document'
        data = xhr.responseXML
        data = xhr.response
    App.onLaunch = (options) ->
      xhr = new XMLHttpRequest
      xhr.onreadystatechange = ->
        if xhr.readyState == 4
          xml = readBody(xhr)
          console.log xml
          parser = new DOMParser()
          doc = parser.parseFromString xml, "application/xml"
          doc.addEventListener "select", (event) ->
            el = event.target
            videoURL = el.getAttribute("videoURL")
            if videoURL != null && videoURL != ""
              player = new Player()
              playlist = new Playlist()
              mediaItem = new MediaItem("video", videoURL)
              player.playlist = playlist
      xhr.onerror = ->
        errorDoc = createAlert("Evaluate Scripts Error", "Error attempting to evaluate external JavaScript files.")
      xhr.open "GET", "#{options.BASEURL}/videos.xml", true
      xhr.send null

    This is adds a readBody helper to make handling XHR responses a bit easier and sets up the application to load our videos.xml file. Note that for some reason tvOS applications require a fully qualified URL when loading resources (you can’t just read /videos.xml).

    7. Try it out

    Now if everything’s gone right, you can go back to Xcode and run the app in the simulator again with ⌘ + R. You should see a more useful tvOS app! To navigate around, enable the Apple TV Remote from the Edit menu.

    The final app in the simulator

    Get the project files

    You can find the project files on GitHub:

  • Piranhas for iOS 1.1

    Piranhas for iOS version 1.1 is now available on the App Store.

    In addition to native book cover previews this release also contains some minor bug fixes.

    Piranhas helps you save money by comparing prices between Amazon stores, the Book Depository, and Wordery. It’s super easy because we calculate the shipping costs and currency conversions for you.

    Download on the App Store

    Or, alternatively, go to www.piranhas.co to use Piranhas in your browser.

  • Saukko Stout Revisited

    Last weekend I took a trip to nostalgia land and cracked open a bottle of one of the first beers I ever made: Saukko Stout.

    Saukko Stout bottle and pint

    Saukko Stout was my third beer, brewed all the way back in February 2014 (bottled on March 2014). This means that it had been bottle conditioning for about 18 months at this point (and not in any particularly optimal setting).

    Considering its age and storage conditions (and the fact that it was one of my first beers), it was actually pretty decent. I’m not sure I’ll be rushing off to brew another gyle of it, but it was eminently drinkable and enjoyable. Definitely far better than the bottle of Ilves IPA I had back in January at any rate…

    PS. Check out my free iOS apps Beer Styles 2 and Piranhas for iOS

  • Beer Styles 2

    Beer Styles 2 has been available in the App Store for a few days now. Beer Styles 2 is a big update and has several swell improvements, especially for iOS 9 devices.

    BJCP 2015 and 2008 guidelines

    2015 and 2008 guideline switcher

    Just like the previous version this includes both the 2008 and 2015 guidelines (and quite a few small formatting and typo corrections).

    Slide Over and Split View

    On iOS 9 you can use Slide Over and Split View on iPads to multitask while browsing style guidelines. Very handy if you’re trying to build a recipe in one app while referring to the style guideline!

    Beer Styles in a Split View

    Spotlight search integration

    Beer Styles now has Spotlight integration so you can search for style guidelines without having to first launch the app. Just swipe down on the home screen…

    Beer Styles Spotlight search

    Nicer sharing

    Sharing individual styles is now nicer because your friends can now see the same view as you do, even if they don’t have Beer Styles installed or if they don’t even have an iOS device.

    Beer Styles on the web

    Still free

    There’s still one thing that hasn’t changed: Beer Styles is still completely free!

    Download on the App Store

    PS. If you like Beer Styles (or even if you don’t), you might like to try my latest iOS app: Piranhas for iOS. Piranhas is a free book price comparison tool for the web and iOS.

    Oh, and if you like using Beer Styles, a review on the App Store would be highly appreciated…

  • Piranhas for iOS

    I’m very excited to announce that there’s now a native iOS app for Piranhas and it’s available on the App Store for free! It has all the features of the web version plus barcode scanning.

    Piranhas for iOS on an iPad and iPhone 6s


    • Scan ISBN barcodes from books
    • Supports Split View and Slide Over on iOS 9 iPads
    • Supports iOS 9’s new in-app browser
    • Shipping charge estimates
    • Automatic currency conversions

    Supported stores:

    • Amazon.com (United States)
    • Amazon.co.uk (United Kingdom)
    • Amazon.ca (Canada)
    • Amazon.de (Germany)
    • Amazon.es (Spain)
    • Amazon.fr (France)
    • Book Depository
    • Wordery

    Download on the App Store

  • Beer Styles 1.1.0 + BJCP 2015

    Version 1.1.0 of Beer Styles just hit the iOS App Store. The biggest new feature is that the BJCP 2015 Style Guidelines are now included!

    Beer Styles 1.1.0 on an iPad and iPhone 6

    The 2015 and 2008 beer, cider, and mead style guidelines from the Beer Judge Certification Program (BJCP).

    The style guidelines describe what each style of beer, cider, and mead should taste, look, and smell like.

    The guidelines include vital stats such as OG (original gravity), FG (final gravity), IBU (bitterness), SRM (colour), and ABV readings as well as descriptions for aroma, appearance, flavour, and mouth feel.

    In Beer Styles all the vital stats have been given a visual representation to make it easier to fathom how differences in styles.

    Download on the App Store

  • Bad Tidings

    Bad Tidings is a beer made from a bunch of leftover malts I had accumulated. I decided to try for a Belgian style beer largely because the Belgians seem to have a pretty relaxed attitude about what adjuncts they use, plus it gave me a chance to try out the Fermentis T-58 (PDF link) yeast.

    I chose the name Bad Tidings because I wanted to invoke a certain sense of dread or foreboding with the name (as it was unsure whether the beer would turn out to be any good at all).

    For this label I went with a bit of a literary theme because Bad Tidings sounds like the title of an 18th or 19th century novel in my head. The sole typeface is Adobe Caslon Pro, chosen mostly of how it looks but also because Caslon seemed to be historically accurate for the 18th century.

    “Practically all American and English printing from 1735 to 1800 was done using the Caslon font.” (source)

    As for the beer itself, the small sample I tasted while bottling seemed pretty good but I’ll have to wait a couple of weeks for it to bottle condition before rendering a final verdict.

    Bad Tidings label

  • The Summoning Dark

    The Summoning Dark is my first Black IPA, it’s named after a character in Thud!, a book by Terry Pratchett (if you can call a pan-dimensional creature and spirit of vengeance a character).

    For the main graphic of the label I used the mine sign rune for the Summoning Dark (described in the book as “a floating eyeball with a curly tail”). The beer name itself is in a rune-like typeface. The labels are stamped with my brewery stamp.

    The beer itself came out quite well, though on the brew day I worried about the colour coming out too light, but that turned out to be a false alarm in the end.

    As an attempt to minimise the “roasty” flavours from the Carafa Special II, instead of mashing it with the rest of the grains, I cold steeped it overnight in about two litres of water. I filtered out the malt from the water and added it to the wort before beginning my boil.

    In the end I think the beer got approximately the right about of the roasted flavours, but I it could have been more heavily hopped (especially in terms of aroma).

    The Summoning Dark

  • An improved email address validator

    For the last couple of years we’ve mostly used Devise’s extremely simple email address regex to validate email addresses. After all, the real proof of whether an address is correct is if the confirmation email gets through to the end user…

    # Email regex used to validate email formats. It simply asserts that
    # one (and only one) @ exists in the given string. This is mainly
    # to give user feedback and not to assert the e-mail validity.
    config.email_regexp = /\A[^@]+@[^@]+\z/

    If you haven’t read it already, you should read David Celis’s Stop Validating Email Addresses With Regex blog post for a more detailed explanation of why you shouldn’t validate email addresses with regexes.

    The downside of this regex is that it can and will let through email addresses that raise Mail::Field::ParseError exceptions once Mail actually tries to parse them.

    For example, some non-latin characters (such as Ä, Ö, or Å) cause explosions:

    re = /\A[^@]+@[^@]+\z/
    email_address = "matti.meikäläinen@example.com"
    puts "Valid" if email_address =~ re
    #=> Valid
    Mail::Address.new email_address
    # Mail::Field::ParseError: Mail::AddressList can not parse |matti.meikäläinen@example.com|
    # Reason was: Only able to parse up to matti.meikä

    Obviously if Mail can’t parse the email address the email won’t be sent to the user…

    My latest approach is to combine the Devise regular expression validation with an additional check that the Mail library can actually parse the user supplied address:

    # app/validators/email_validator.rb
    class EmailValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        email = Mail::Address.new(value)
        unless email.address =~ Devise.email_regexp
          record.errors.add(attribute, :invalid_email)
      rescue Mail::Field::ParseError => e
        record.errors.add(attribute, :invalid_email)

    If you’re wondering why we need to check that the email address both passes the regex and Mail::Address.new(…), it’s because Mail will happily accept a string such as foo as a valid address:

    email = Mail::Address.new "foo"
    # => #<Mail::Address:2309836100 Address: |foo| >
    # => "foo"

    It would also be possible to pass Mail’s error message back to the user to give her a more detailed explanation of why the email address looks invalid, though this may be more difficult if your app uses internationalisation (as is the case with many of our apps).

    If you enjoyed this, you might enjoy learning more about why emails are generally pretty terrible.

  • Milwaukee MA871

    My latest beer making toy was delivered today: a Milwaukee MA871 digital refractometer.

    I briefly tested it on a couple of samples of water and sugar water and the experience was definitely nicer than trying to peer at the scale through my (admitedly cheap) optical refractometer.

    While perhaps not exactly a necessity over hydrometers or optical refractometers, I’m still really excited to get to use this in anger.