The personal blog of Stuart Breckenridge

Control Center States in iOS 11

Ryan Jones (via Twitter):

Absolutely nails the difficulties in understanding the current Control Center UI.


The End of Microsoft Paint  

Zoe Klienman:

Microsoft’s graphics program Paint has been included in a list of Windows 10 features that will be either removed or no longer developed.

Paint has been part of the Windows operating system since its release in 1985 and is known for its simplicity and basic artistic results.

I was never a frequent user of Paint other than for cropping1 or resizing screenshots. However, what has stood out over the last few years is the artwork from Jim’ll Paint It. It continually cropped up on my Facebook feed and, in my opinion, gave Paint a hilarious new lease of life (for a short while).2

Update (2017-07-24): Paint will not be retired. It’ll just be moved to the Windows Store.

  1. Before the Snipping Tool arrived. ↩︎

  2. Check out: Roger Moore Bond Villain Reunion and Kebabba the Hutt ↩︎


Subscription Pricing

Subscription pricing is a contentious issue when it comes to software. Two great articles caught my attention over the last few days.

Michael Tsai:

It’s certainly true that people are wary of subscriptions. But I wonder how much of the recent backlash is due to the subscription model itself and how much is due to the fact that, in practice, transitions to subscriptions have effectively been large price increases.

Nick Heer:

While Tsai points out that subscriptions have increased the price of software for their typical lifespan, let’s not forget that some people are comfortable using an older version of software for longer.

I agree with both of these points of view. However, I also believe that people are wary of subscriptions because they don’t know what will happen at the end of their subscription period. Based on my subscriptions, if I stop paying at the end of the subscription period or otherwise cancel:

  • Creative Cloud: I’d lose access to Photoshop and Lightroom, making the apps useless
  • Office 365: I’d lose access to the Office Suite, making the apps useless
  • TextExpander: Snippets would stop expanding, making the app useless
  • 1Password: My account would be frozen but existing items would be usable
  • WebStorm: I’d lose access to future updates, but I’d have a perpetual license for the latest version of the software as at the end of my subscription

Of all those, I think WebStorm provides a reasonable middle ground in a subscription model: when you cancel you’ll have a perpetual license to use the latest version of the software as at the end of your subscription. It’s the most equitable solution for consumer and developer, and, to Nick’s point, lets people use the software version that they are happy with, while not paying for future releases that mean nothing to them.


App Store Receipt Validation with Node and Express  

I just released my first Node package: itunes-validation. The package provides functionality to validate app receipts with the App Store, in either sandbox or production scenarios. Here’s an example of how to use the package:

$ git clone https://github.com/stuartbreckenridge/itunes-validation.git
$ cd itunes-validation
$ npm install
$ npm start

Once the app is running, two endpoints are available with the following query parameters:

  • GET /0.1/sandbox (for sandbox receipt validation)
  • GET /0.1/production (for production receipt validation)
Parameter Required Description
receipt Yes Base 64 encoded receipt string.
secret No Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
exclude No Only used for iOS7 style app receipts that contain auto-renewable or non-renewing subscriptions. If value is true, response includes only the latest renewal transaction for any subscriptions.

If you are testing with the sandbox, then the below sample code should provide some pointers.1

// Struct which represents receipt data returned by Apple.
struct Receipt: Decodable {
    var receipt: [String:String]
    var status: Int
}

// Function to validate receipt
func obfuscatedValidationMethod() {
    let receiptData = NSData(contentsOf: Bundle.main.appStoreReceiptURL!) // get receipt data
    let base64Receipt = receiptData?.base64EncodedString(options: .endLineWithLineFeed) // Convert to base 64

    var valUrl = URLComponents(string: "http://localhost:8443/0.1/sandbox") // create URL
    let queryItems = [URLQueryItem(name: "receipt", value: base64Receipt)] // create query
    valUrl?.queryItems = queryItems // add query items to URL
    let request = URLRequest(url: valUrl!.url!) // create URL request with URL
    
    let session = URLSession.shared // create URLSession
    /* Create task */
    let task = session.dataTask(with: request) { (data, response, error) in
        guard let responseData = data else {
            return 
        }
        let decoder = JSONDecoder()
        do {
            let decodedReceipt = try decoder.decode(Receipt.self, from: responseData)
            if decodedReceipt.status == 1 {
                // Do something with invalid receipt.
            }
        } catch {
            // Handle error.
        }
    }

    task.resume()
}

Links:

  1. Swift 4 code. For brevity, I also assume there is receipt data(!). ↩︎


1Password for iOS Now Auto-Copies One-Time Passwords

1Password is an app I cannot live without. It’s the first app I install on a new iOS or macOS device. However, a long bugbear has always been the way the app handles one-time passwords:

  • you’d be on a login page;
  • you’d open 1Password to get the username and password;
  • the username and password would be inserted;
  • then a one-time password page would be presented;
  • you’d go back into 1Password to copy the one-time password; and,
  • then you’d paste the one-time password

It was a laborious process, but, thankfully, that’s no longer the case. In v6.8, released yesterday, a new feature was added:

One-time passwords now copy themselves to the clipboard automatically whenever you fill an item that has a one-time password.

It’s such a welcome feature. 👍🏻


Apple Previews New Emoji  

In celebration of World Emoji Day Apple has posted a nice preview of emoji coming later this year with iOS 11.

My personal favourite is the Exploding Head emoji.


Net Neutrality  

Tomorrow, July 12th, is Battle for the Net day. If you’re not sure why net neutrality is important, consider this brief write up:

The FCC wants to destroy net neutrality and give big cable companies control over what we see and do online. If they get their way, they’ll allow widespread throttling, blocking, censorship, and extra fees.

While I don’t live in the U.S., I do consider the internet to be a shared resource and something we should all look after1. I am supporting this call to action by having the Battle for the Net widget on my site tomorrow. You should too.

  1. Similar to our planet, which is something else the Trump administration are actively neglecting. ↩︎


Downloading the WWDC 2017 Sample Code

Downloading all the sample code from WWDC is a fairly tedious task. However, Johannes Fahrenkrug has created a handy RubyGem to download all the sample code for you. The code is available on GitHub and the gem is really easy to use.

It should be noted that all the sample code comes to 393.5 MB.


The Correct Way to Initialise a UITableViewCell

Below I present four options for initialising a UITableViewCell. Of these options, I tend to use the second option (or a close variant) in production code, and for brevity, option 4 in sample code.

What do you prefer? Are there other, safer options available?

Option 1:

var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
if cell == nil {
    cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
}
return cell

Option 2:

guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) else {
    let newCell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
    return newCell
}
return cell

Option 3:

if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
    return cell
} else {
    let newCell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
    return newCell
}

Option 4:

let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
return cell


Simple JSON Parsing with Codable

As I rewrite Amazing Flag Quiz for iOS 11, one of the greatest things I’ve come across is the new Codable protocol in Swift 4. Before I get into why it’s great, here’s a bit of background: Amazing Flag Quiz v1 to v2.1.2 (current) contains an XML file of ids, country names (short and long, I don’t know why!), continent names, flag image names, and population (I don’t know why!). That file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<countries>
  <country>
    <ID>0</ID>
    <shortCountryName>Afghanistan</shortCountryName>
    <longCountryName>Afghanistan</longCountryName>
    <countryFlag>Afghanistan.png</countryFlag>
    <countryPopulation>25500100</countryPopulation>
    <countryContinent>Asia</countryContinent>
  </country>
  <country>
    <ID>1</ID>
    <shortCountryName>Aland</shortCountryName>
    <longCountryName>'c5land Islands</longCountryName>
    <countryFlag>Aland.png</countryFlag>
    <countryPopulation>28355</countryPopulation>
    <countryContinent>Don't Use</countryContinent>
  </country>
  ...
</countries>

To use this file meant creating an NSXMLParser and all the code that goes along with it. For version 3 of Amazing Flag Quiz, I’ve decided to convert the XML to JSON and use a Country struct that conforms to Decodable.

The tidied up JSON file looks like this:

[
  {
  "name": "Afghanistan",
  "game": "Asia",
  "excludeFromWorldChallenge": false,
  "flag": "AF.png"
  },
  {
  "name": "Albania",
  "game": "Europe",
  "excludeFromWorldChallenge": false,
  "flag": "AL.png"
  }, 
  ...
]

The Country struct mirrors the data set:

/// Representation of data as held in the countryFlags.json file.
struct Country: Decodable {
    var name: String
    var flag: String
    var game: String
    var excludeFromWorldChallenge: Bool
}

I then have a single function to extract a Country array for the game type the player picks:

func flags(for gameType: GameType?) -> [Country] {
    
    let decoder = JSONDecoder()
    let data = try? Data(contentsOf: file!)
    let decodedData = try? decoder.decode([Country].self, from: data!)
    
    guard let countries = decodedData else {
        return []
    }
    
    if gameType == nil {
        return countries
    }
    
    if gameType == GameType.world {
        return GKRandomSource.sharedRandom().arrayByShufflingObjects(in: countries.filter({ $0.excludeFromWorldChallenge == false })) as! [Country]
    }
    
    return  GKRandomSource.sharedRandom().arrayByShufflingObjects(in: countries.filter({ $0.game == gameType!.rawValue })) as! [Country]

}

If a player selected the Asia game type:

let gameArray = flags(for: .asia) // returns a shuffled array of Asian countries.

Codable has made JSON parsing so simple. I’d recommend the Ulimate Guide to JSON Parsing With Swift 4 by Ben Scheirman as further reading.