Ben Dodson

Freelance iOS, macOS, Apple Watch, and Apple TV Developer

Kylo Ben

I have three main hobbies; coding, video games, and LEGO. With the first, I’m fortunate to have a flexible job that allows me to code on tons of different and varied projects every day. With the last, I’ve literally run out of space for the models I already have (most of which are over 2000 bricks in size). Video games, then, are really my main hobby. In the last year I’ve started a podcast on co-operative gaming, built a powerful gaming PC, and spent somewhere in the region of 900 hours playing games. Today I’m happy to announce a new website I’ve been working on dedicated to my gaming hobby; it’s called Kylo Ben:

Kylo Ben is divided into several different sections:

Articles

I’m currently varying between reviews, things I’m excited about, and general thought pieces on the industry. So far, I have published:

I’m trying to write new pieces every couple of days and it has proven to be a good way to practice my writing and get some long form thoughts out that don’t fit neatly into a 140 character tweet. It also means I can keep these game centric pieces away from my more work-oriented blog here at bendodson.com.

About

I’ve listed a few things about my current gaming setups on my about page. It details the components in my gaming PC, my current console and mobile gaming systems, and a look through the various consoles I’ve owned since I started gaming in 1991.

Games

I have a single page which lists all of the games I currently own on both Steam and my Xbox One. Each game has more details including purchase date, last played, total gaming time, and a short note about where I’m currently at with the game. I also list any articles related to that game for easy reference (and vice versa). For example, here is the entry for Mini Metro.

Gaming Time

This is perhaps my biggest achievement; a complete log of time spent on each game. The page updates daily and will list each game I’ve played over the last 2 months along with duration and a link through to more details. This is done through a combination of two custom scripts I wrote to scrape both my Steam library and Xbox Live / Windows 10 account periodically for changes. It’s been fascinating for me to see exactly how long I’ve spent on certain games (*cough* Peggle 2 *cough*) and will allow me to easily write a regular piece on games I’ve played over the previous month.

I’m really excited to be writing regularly about my biggest hobby and I’m looking forward to posting frequently for the foreseeable future. I have a few extra things to announce soon including some apps and game mods so follow me on Twitter for the latest updates. Alternatively, there is an RSS feed available for Kylo Ben which will keep you up to date on any new articles. If you have any thoughts or suggestions for improvements, please get in touch.

Working Remotely

I was recently asked by Remote.co to take part in a new Q&A section about remote workers. You can read the full Q&A on their website but I’ve put some of their questions and my answers below:

What advice would you give someone considering going remote?

Discipline. It takes a lot of discipline to work remotely as you’ll find that it is very easy to put off a piece of work when you’re sitting at home. There are no firewalls stopping you from accessing Facebook and Twitter, the Xbox might be sat saying “just one more game”, and before you know it a month has passed and you’ve done nothing.

How do you avoid becoming distracted when working remotely?

I have a pair of noise-cancelling headphones that I bought for working in an open-plan office but they work at home for me as the act of putting them on puts me into “work mode”. The only problem was that if the doorbell went (sidenote: great advantage of working from home is you never miss an Amazon delivery) I wouldn’t hear it; I fixed that by hooking it up to a Philips Hue lightbulb on my desk so I get a visual alert.

Do you have a dedicated home office?

I do have a dedicated room for work. I have a small desk with a 5k iMac and an external 1080p monitor in portrait mode (I find this better for webpages and iPhone simulators). Apart from the computer, my desk only has space for my tea and water and a Blue Yeti microphone I use for podcasting and Skype. I have lots of bookshelves (mainly full of LEGO) and a small orchid by the window.

What’s the hardest part about working with a traditional team while you’re remote? How do you overcome it?

I find the hardest thing is that traditional teams will want to have meetings all the time. They also want you to pop into the office fairly frequently for reasons unknown to me. I’m pretty good at telling prospective clients that I only work remotely and that I don’t do daily catch up calls; I get a good handle for the project and then do a week or two of work before sending over a build for feedback.

MailRoute blacklisting via email

I’ve been using MailRoute on and off for the past few years1 in an effort to reduce the amount of spam in my inbox but there is one missing feature that always drives me nuts; there is no way to blacklist an email address or domain without going to their website. Whilst you can whitelist domains easily from the daily digest of caught spam, there is no feature that lets you forward spam to them in order to have it blacklisted (despite people, including me, requesting it since 2013). After having a look at the MailRoute API, I decided it was time to fix this myself.

There are several components in getting this working.

  1. I created a new email address with Gmail which will catch my forwarded spam2.
  2. I set up an account with Context.io and connected it to the gmail account3.
  3. I put the following code available on GitHub on a 15 minute CRON job
<?php

require_once 'OAuth.php';

define('CONTEXT_KEY', 'your-contextio-consumer-key');
define('CONTEXT_SECRET', 'your-contextio-consumer-secret');
define('CONTEXT_USER_ID', 'your-contextio-user-id');
define('CONTEXT_TRASH_NAME', '[Gmail]/Trash'); // change if not using Gmail

define('MAILROUTE_USER', 'your-mailroute-username'); // should be your email address
define('MAILROUTE_API_KEY', 'your-mailroute-api-key');
define('MAILROUTE_EMAIL_ACCOUNT_ID', 'your-mailroute-email-id'); // numeric - can be found in admin.mailroute.net network traffic

$whitelist = ['sending@example.com']; // add the email address you are forwarding from or you'll blacklist yourself...

$inbox = performOAuthRequest('GET', 'https://api.context.io/lite/users/'.CONTEXT_USER_ID.'/email_accounts/0/folders/inbox/messages');
foreach ($inbox as $message) {
    $id = $message->{'message_id'};
    $body = performOAuthRequest('GET', 'https://api.context.io/lite/users/'.CONTEXT_USER_ID.'/email_accounts/0/folders/inbox/messages/'.$id.'/body');
    $content = str_replace('<', '', str_replace('>', '', $body->bodies[0]->content));
    $emails = extractEmailAddresses($content);
    foreach ($emails as $email) {
        if (!in_array($email, $whitelist)) {
            performRequest('POST', 'https://admin.mailroute.net/api/v1/wblist/', ['email_account' => '/api/v1/email_account/'.MAILROUTE_EMAIL_ACCOUNT_ID.'/', 'email' => $email, 'wb' => 'b'], ['Authorization: ApiKey '.MAILROUTE_USER.':'.MAILROUTE_API_KEY]);
        }
    }
    performOAuthRequest('PUT', 'https://api.context.io/lite/users/'.CONTEXT_USER_ID.'/email_accounts/0/folders/inbox/messages/'.$id, ['new_folder_id' => CONTEXT_TRASH_NAME]);
}

echo 'DONE!';



// CURL

function performOAuthRequest($httpMethod, $url, $params=[], $headers=[]) {
    $signatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
    $oauthConsumer = new OAuthConsumer(CONTEXT_KEY, CONTEXT_SECRET, NULL);
    $oauthRequest = OAuthRequest::from_consumer_and_token($oauthConsumer, NULL, $httpMethod, $url, $params);
    $oauthRequest->sign_request($signatureMethod, $oauthConsumer, NULL);
    return performRequest($httpMethod, $oauthRequest, $params);
}

function performRequest($httpMethod, $url, $params=[], $headers=[]) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);

    $headers[] = 'Content-Type: application/json';
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    if (!in_array($httpMethod, ['GET', 'POST'])) {
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $httpMethod);
    }
    if ($httpMethod != 'GET') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
    }
    $output = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return json_decode($output);
}



// Email parsing

function extractEmailAddresses($string) {
   $emails = array();
   $string = str_replace("\r\n",' ',$string);
   $string = str_replace("\n",' ',$string);

   foreach(preg_split('/ /', $string) as $token) {
        $email = filter_var($token, FILTER_VALIDATE_EMAIL);
        if ($email !== false) { 
            $emails[] = $email;
        }
    }
    return array_unique($emails);
}

Now all you need to do is forward any spam you receive to the Gmail account you set up. Every 15 minutes, the CRON job will run and check the inbox for any emails. When it finds some, it will parse them for email addresses and blacklist them with MailRoute before deleting them.

In an ideal world, MailRoute would implement a custom email address for blacklisting but this seems to be working for now.

  1. I stopped using MailRoute a while back and was using the black hole feature of Sanebox but I’ve just stopped using them as found the free service FollowUpThen - I was getting a lot of spam after the switch off so decided to re-activate MailRoute this week. I use Fastmail for my email in case you were wondering. ↩︎

  2. As you’ll be forwarding emails to Gmail, you’ll want to disable its spam filter as otherwise your emails won’t end up in the inbox for processing by Context. To do this, you’ll need to set up a new rule which matches the email ‘@’ and ensures it never goes to spam. ↩︎

  3. Whilst I could connect Context to my personal email and just move things into a folder for blacklisting, I don’t trust services with access to my email account, certainly not services that are free and are using the data. ↩︎

Pocket Rocket for iOS 10 (now with an iMessage app)

To coincide with the release of iOS 10, I’m happy to announce a free update is available for my app Pocket Rocket which includes several new features:

First of all, I’ve added an optional tile-based interface which uses an image from the article or a random colour if one isn’t available. You can long press a tile to get options for sharing, archiving, and deleting the article and tapping will open the article up in Safari or the in-app browser as per your settings. Next, I added the ability to share articles directly from Pocket Rocket; whether you are using the old list view or the new tile-based one, just long press on an article and you’ll be given a share option which uses the standard iOS share sheet so you can transfer the URL to any compatible app.

Finally, I added an iOS 10 iMessage app so you can share articles directly within iMessage:

This was a really interesting feature for me as it let me get to grips with the new APIs that allow apps to hook into iMessage. There are a few interesting niggles for developers when writing these apps1 but on the whole it is fairly simple and includes a lot more power than I thought they would2. With Pocket Rocket, you get a list of all of your articles; you can then tap to include them in your message and send them. Once received, you can tap to have the article open up in a webview directly within iMessage.

I think iMessage apps are going to be a huge deal so I’m excited that Pocket Rocket is one of the first apps available on the iMessage store.

You can get Pocket Rocket for free on the App Store. If you have any feature requests or feedback, please get in touch.

  1. For example, there is no standard way of seeing when someone tapped a link which is… irritating. Instead you have to work out if a particular message is selected (which happens for a variety of reasons) and then be clever about what you present. ↩︎

  2. I’ve seen one app that lets you use the camera within iMessage - pretty cool! ↩︎

Generating a random colour, with a seed, in Swift 3

I’ve been working on some iOS 10 updates to Pocket Rocket1 which led me to trying to solve an interesting problem; how do you generate a random colour, with a seed, in Swift? The reason for this dilemma is that I’m going to start showing a grid view of Pocket articles and many of them do not have images associated with them. In order to break things up, I decided I’d like a random colour for the background of each article:

As each article is UICollectionViewCell, setting a random background colour is not suitable as when you scroll and the cells are re-used the backgrounds will change. Whilst that could be fixed fairly easily, I also want the colours to persist with the article between app launches and also be the same in the iPad app and the iMessage extension. This means I need to generate a random colour with a seed so that colours generated with the same seed will always be the same.

Thankfully this is fairly easy if we use the srand48 and drand48 functions:

func randomColor(seed: String) -> UIColor {
    
    var total: Int = 0
    for u in seed.unicodeScalars {
        total += Int(UInt32(u))
    }
    
    srand48(total * 200)
    let r = CGFloat(drand48())
    
    srand48(total)
    let g = CGFloat(drand48())
    
    srand48(total / 200)
    let b = CGFloat(drand48())
    
    return UIColor(red: r, green: g, blue: b, alpha: 1)
}

We start by taking a string (in my case an article title) and turning it into an integer by getting the sum of the unicode scalar values of each character. Next, we seed the drand48 function by using srand48 with our Int along with a multiplier or division so that the red, green, and blue values are all different. Finally, we compile the UIColor together.

I’ve put together a Swift Playground demonstrating this using the last few headlines from Daring Fireball:

As you can see, each article title results in a different colour; the same string will always result in the same colour being generated. Be aware that this is not collision free as it is just a basic addition of scalar values – a string such as “ben” and “neb” will result in the same colours. This isn’t an issue for my usage and still serves as an interesting way of seeding random colours.

  1. Specifically an iMessage app↩︎

Tips for Remote Working

I was recently asked by Inc.com to contribute to a feature asking several successful remote workers to share their favourite tips and tools to sustain productivity when working remotely. You can read the full article on their website but I’ve also put their questions and my answers below:

How long have you worked remotely?

Around 6 years

What does a typical day look like for you?

I’ll wake up and go and deal with my dogs first thing around 6.30am whilst getting breakfast ready for my wife. Once she has left, I’ll read and deal with any overnight emails before doing a concentrated burst of work until around 1pm. After lunch, I’ll typically go for a walk and do a bit more work before playing some video games on my PC before my wife gets home. We’ll then walk the dogs, cook some dinner, and generally watch TV. Once she falls asleep, I’ll either do some more work or play some video games depending on how busy my schedule is.

What’s the one tip or tool you’d give to anyone looking to get started remote working to keep productive?

Develop a schedule and stick to it. The hardest thing in the world is finding the motivation to start on a new project but once you’ve done 10 minutes of work you’ll be raring to go on the next 10 minutes and the next. To work remotely, you need the self-control to work when you don’t want to, the passion in your work to keep going, and the common sense to know when a 30 minute break is going to actually help rather than hinder you.

With regards to tools, I can strongly recommend Cushion as an incredible way of managing your schedule, invoices, and expenses; it’ll make sure you don’t get overbooked (a big no-no) and also help you manage your finances better.

Brapp: Summer Updates

Over the summer, I’ve continued working with Brapp on 3 big releases which added several new features and UI updates.

v2.1

Released in July, Brapp 2.1 added the ability to trim recorded videos and change vibrancy of video filters along with adding a load of speed and stability improvements. However, the big new feature allowed users to export their beats as a video complete with an animating waveform:

This was a really interesting feature for me to build as I wasn’t sure initially how it would work. It turns out that it is actually fairly easy by adding your animations directly to the various CALayers and then using an AVVideoCompositionCoreAnimationTool as your AVMutableVideoComposition animation tool. Fun stuff!

v2.1.1

Released just a few days later, Brapp 2.1.1 included the ability to pin your favourite beat or post to the top of your profile feed1 and added a “Now Playing” bar along the bottom of the app with a similar style to Apple Music. It also introduced synchornicity between the website and app for notifications which is an interesting problem to solve; basically, if someone reads the notifications on the website, a silent push needs to be sent to the app in order to remove the badge icon so you don’t think you have new notifications. There were also some pretty meaty performance upgrades behind the scenes.

v2.2

Brapp 2.2 was released today with the headline feature of location support. One of my main interests with iOS apps has always been in geo-location so it was really fun for me to add these new features including the ability to tag specific locations to beats and videos, add a city or town to your profile, view beats and videos on a profile map, and search for beats and videos either near you or at a specific location.

Once again, Brapp has been a really fun project to work on. I can’t say enough good things about the people behind it and am looking forward to working with them again in the future. If you haven’t checked it out yet, you can get Brapp for free on the App Store. You can also read about the initial build and release, the v1.1 update last November, and the v2 update earlier this year.

  1. This feature was added by David Gibson↩︎

Moonlight tvOS for Apple TV 4

A few nights ago I was attempting to finish playing Day of the Tentacle on my PC via a Steam Link in my bedroom. For some reason, whilst it all loaded correctly, it was really jerky to play with a severe delay between controller input and response on the screen. As I’d been playing it successfully using GameStream on my Nvidia Shield, I assumed correctly that the issue was with the Steam Link.

Not content to have to either a) go downstairs and use the Nvidia Shield or b) go to my office and play in front of my PC, I instead turned to my Apple TV as I knew there was an open source project called Moonlight that basically lets you run the GameStream technology on non-Nvidia devices like iPhone or iPad. I assumed there would be a version for Apple TV but unfortunately it turns out that the AVFoundation frameworks have a __TVOS_PROHIBITED flag on some key components that prevent it from working. That’s when I found a fork by kevsmithpublic that enabled tvOS support with some code changes and requiring you to remove the __TVOS_PROHIBITED flags from the AVFoundation framework1. Unfortunately, I wasn’t able to get it to pair to my PC and after a lot of searching it turns out that the fork was fairly old and hadn’t been updated to the latest specifications for GeForce Experience 3.0 and above.

Determined not to be beaten, I forked the project and then set about updating the code to support the latest Moonlight connections. This ended up being similar to the point and click adventure I was trying to play as I needed to constantly fetch the Apple TV from my bedroom, connect it to my mac, build the project, unplug the Apple TV, reconnect it in my bedroom, and then test if it actually worked (all because I didn’t have a spare monitor in my office as I use an iMac). After several hours (and a few beers) I had the project working and can now stream my games at 1080p from my PC to my Apple TV 4 and play them using an MFI controller.

You can download the project from my GitHub account.

  1. This means it can’t ever be used in an App Store version but it’s pretty cool nonetheless. I had no idea you could alter framework headers to enable things like this. It does beg the question why this framework is prohibited as it all clearly works. ↩︎

The Divide #15 - Virtually Reality

After a short summer break we’re back with episode 15 of The Divide podcast which is all about virtual reality. John has a fancy new HTC Vive, I’m trying to convince myself I don’t need one, and Chris just wants to be on the bridge with Captain Janeway…

You can get The Divide from these fine outlets:

Don’t forget to leave a review on iTunes and follow us on Twitter via @PodcastDivide.

Media Library privacy flaw fixed in iOS 10

As I’ve mentioned many times before, iOS had a pretty terrible privacy flaw in that apps didn’t need any permissions in order to read through your media library. This was an issue as it meant you could be fingerprinted easily and tracked across various apps1. Thankfully, this has now been fixed in iOS 102.

In this article, I’ll explain how to update your apps to support this new privacy requirement. Before I do that, I’m going to show you what happens if you run an app built against the iOS 9.3 SDK (or earlier) on iOS 10:

[app name] would like to access Apple Music

You’ll be prompted3 to allow permissions as soon as any media library code is encountered be that on app startup or in a background process such as that used by Music Tracker. If you decline to give permission then the app will quit to the home screen and you will not be able to use it. This is a big change from previous permission switches whereby apps built against the old SDK would be exempt from new permissions (i.e. if your iOS 7 SDK compiled app ran on iOS 8, it wouldn’t crash because it wasn’t using the new location privacy options). Personally I like this change as it allows you to see clearly which apps were abusing these APIs such as Canary and Google Calendar.

Bottom line: If you are using MPMediaQuery or similar in your app, you’d better update it with the iOS 10 SDK as soon as possible as otherwise you are going to get a lot of crashes if your users don’t allow the permission (or a lot of awkward questions if you shouldn’t be using this API).

How do you update your app to request permission for these APIs? First of all, lets whip up a basic example with Swift 3 that will pull the title of the first track in your music library:

@IBAction func buttonPressed(_ sender: AnyObject) {
    let query = MPMediaQuery.songs()
    if let items = query.items, let item = items.first {
        NSLog("Title: \(item.title)")
    }
}

In keeping with other permissions based APIs such as photos, contacts, and calendars, iOS 10 requires that you add a new key to your Info.plist file to explain why you want to use this permission; for music library access, this key is NSAppleMusicUsageDescription. If you don’t add this key, your app will crash as soon as you try and access an MPMediaQuery with the following message:

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSAppleMusicUsageDescription key with a string value explaining to the user how the app uses this data.

With the NSAppleMusicUsageDescription key in place, you will now be given a standard permission dialogue when you first try and access the users media library. If the user chooses “Don’t Allow”, then the media query will fail and any subsequent calls will request in the query.items property being nil. If they choose “OK”, then nothing happens (as execution of the code is not suspended and there is no callback). To fix this, we need to use the MPMediaLibrary.authorizationStatus() and requestAuthorization((MPMediaLibraryAuthorizationStatus) -> Void) APIs that were added in iOS 9.3 to do something like this:

@IBAction func buttonPressed(_ sender: AnyObject) {
    MPMediaLibrary.requestAuthorization { (status) in
        if status == .authorized {
            self.runMediaLibraryQuery()
        } else {
            self.displayMediaLibraryError()
        }
    }
}

func runMediaLibraryQuery() {
    let query = MPMediaQuery.songs()
    if let items = query.items, let item = items.first {
        NSLog("Title: \(item.title)")
    }
}

func displayMediaLibraryError() {
    var error: String
    switch MPMediaLibrary.authorizationStatus() {
    case .restricted:
        error = "Media library access restricted by corporate or parental settings"
    case .denied:
        error = "Media library access denied by user"
    default:
        error = "Unknown error"
    }
    
    let controller = UIAlertController(title: "Error", message: error, preferredStyle: .alert)
    controller.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    present(controller, animated: true, completion: nil)
}

You first need to request authorization and then run your code only if the status is authorized. If not, then you should display an error specific to whether or not the request was denied or restricted (usually by corporate or parental controls).

I’ve put an example project on GitHub which uses the code above by way of demonstration.

I’m extremely grateful to everyone that has helped share my posts about this issue and to the engineers at Apple for fixing this privacy flaw. I’ll update this post should anything change between now and the expected public release of iOS 10 in September.

  1. Whilst I had no concrete proof of this at the time of writing those articles, it looks like I was right that this was a widespread problem↩︎

  2. I don’t want to take all the credit for this but I honestly don’t think this would have been fixed if I hadn’t raised the issue repeatedly over the last 7 months. ↩︎

  3. In earlier builds of iOS 10 you’d be asked “[App Name] Would Like to Access Apple Music” (as shown in screenshots above) but as of iOS 10 beta 4 this has been changed to the more appropriate “[App Name] Would Like to Access Apple Music and Your Media Library”. ↩︎

« Older Entries Newer Entries »