What is a JSON feed? Learn more

JSON Feed Viewer

Browse through the showcased feeds, or enter a feed URL below.

Now supporting RSS and Atom feeds thanks to Andrew Chilton's feed2json.org service


Stuart Breckenridge


The Diminishing Utility of MFMailComposeViewController

Permalink - Posted on 2021-01-08 12:05, modified on 2021-01-09 22:01

The Distant Past

Before iOS 14, the default email app on iOS was Mail. Of course, you could have had other email apps installed, but they’d never be the app used by the system when tapping on an email address. You’d always end up in Mail.

This made things easy for developers. If you wanted give users the ability to send emails from within your app, you’d use MFMailComposeViewController. Implementation was easy:

if MFMailComposeViewController.canSendMail() {
	let mailController = MFMailComposeViewController(rootViewController: self)
	mailController.setSubject("Test Subject")
	mailController.mailComposeDelegate = self
	present(mailController, animated: true, completion: nil)


As a developer, I want to respect the user’s choice of email app, whether it’s Mail, Edison, Gmail, Outlook, or Hey. To do that, I can’t use MFMailComposeViewController. Instead, I have to add mailto to the LSApplicationQueriesSchemes key in Info.plist and then, when the user wants to send an email, use this code:

if UIApplication.shared.canOpenURL(url) {
	UIApplication.shared.open(url, options: [.universalLinksOnly : false]) { (success) in
		// Handle success/failure

Unlike MFMailComposeViewController, this approach sends the user to their choice of email app and, at the same time, closes the source app. It’s not ideal.

A feature request for iOS 15: default mail apps should have their own MFMailComposeViewController equivalent.

Podcasts Saying Nothing for 20 Minutes

Permalink - Posted on 2021-01-06 00:24, modified on 2021-01-09 22:01

20 Years of SuperDrive

Permalink - Posted on 2021-01-05 13:23, modified at 14:02

Via 512 Pixels:

2001 was a huge year in terms of Apple history.

First SuperDrive

This really caught my attention. The current incarnation of the SuperDrive has remained largely static for 20 years and it’s still available for purchase.

It still has no eject button, it still doesn’t support USB-C, and it still doesn’t support Blu-ray.

Shockingly, it still costs $79. Additionally, to connect it to a modern USB-C MacBook you’d need the $19 adapter.

Thus, to connect the current SuperDrive to a USB-C MacBook, you’d need to spend $98.

Alternatively, you could spend $79.99 on this. It has an eject button, USB-C, Blu-ray, and “good after-sales service”. It also appears to have been designed with tracing paper.

Singapore Breathe — Development Diary #2

Permalink - Posted on 2021-01-04 13:30, modified at 14:33

This post is part of the Singapore Breathe Development Diary Series.


Before we get into this week’s update, some repository updates:

  • A README has been added
  • Continuous Integration (via Bitrise), Code Coverage (via Codecov), and Code Nitpicking Quality (via Codacy) have been implemented
  • The Notes folder contains some additional information on Localisation and the component parts of the air quality measurement

PSI and PM2.5 Data

There are two APIs that I’m using in Singapore Breathe:

  • PSI: This is updated hourly and contains PM10, PM2.5 (24-hour and sub-index), O3, CO, NO2, and SO2 values, with the exception of the PM2.5 hourly readings.
  • PM2.5: This contains the hourly PM2.5 readings only.

The only difference in structure between the two API responses is that PSI API returns readings for six regions (North, East, South, West, Central, and National), while the PM2.5 API returns results for five (it excludes National).

  // PSI
  "items": [
    "timestamp": "2020-12-15T08:00:00+08:00",
    "update_timestamp": "2020-12-15T08:03:50+08:00",
    "readings": {
      "o3_sub_index": {
        "west": 4,
        "national": 4,
        "east": 2,
        "central": 2,
        "south": 3,
        "north": 4
      "pm10_twenty_four_hourly": {
        "west": 20,
        "national": 25,
        "east": 25,
        "central": 21,
        "south": 24,
        "north": 19
  // PM2.5
  "items": [
    "timestamp": "2020-12-15T08:00:00+08:00",
    "update_timestamp": "2020-12-15T08:03:50+08:00",
    "readings": {
      "pm25_one_hourly": {
        "west": 5,
        "east": 3,
        "central": 1,
        "south": 5,
        "north": 2

Given the similarities, both PSI and PM2.5 responses can be modelled using the same struct.

A Combined RegionalAQM

The way I will display the readings to users is via annotations on a Map. For example, there will be an annotation for the West region which, when tapped, will show the air quality measurements for that region. As such, I’m not using the data as-is from the API. Instead, for each region, I am creating a RegionalAQM1 NSManagedObject which contains the distinct readings for that region and timestamp. In order to ensure duplicate readings are not saved to the database, the RegionalAQM has an id property that is a concatenation of region and timestamp, and the NSManagedObjectContext has a merge policy of mergeByPropertyObjectTrump.

An example RegionalAQM looks like this:

let aqm = RegionalAQM(context: PersistenceController.shared.container.viewContext)
let label = psi.regionMetadata.filter { $0.name == region }.first!
aqm.latitude = label.labelLocation!.latitude
aqm.longitude = label.labelLocation!.longitude
aqm.co_eight_hour_max = item.readings!["co_eight_hour_max"]!.north ?? 0.0
aqm.co_sub_index = item.readings!["co_sub_index"]!.north ?? 0.0
aqm.no2_one_hour_max = item.readings!["no2_one_hour_max"]!.north ?? 0.0
aqm.o3_eight_hour_max = item.readings!["o3_eight_hour_max"]!.north ?? 0.0
aqm.o3_sub_index = item.readings!["o3_sub_index"]!.north ?? 0.0
aqm.pm10_sub_index = item.readings!["pm10_sub_index"]!.north ?? 0.0
aqm.pm10_twenty_four_hourly = item.readings!["pm10_twenty_four_hourly"]!.north ?? 0.0
aqm.pm25_one_hourly = pm25.items.first!.readings!["pm25_one_hourly"]?.north ?? 0.0
aqm.pm25_sub_index = item.readings!["pm25_sub_index"]!.north ?? 0.0
aqm.pm25_twenty_four_hourly = item.readings!["pm25_twenty_four_hourly"]!.north ?? 0.0
aqm.psi_twenty_four_hourly = item.readings!["psi_twenty_four_hourly"]!.north ?? 0.0
aqm.so2_sub_index = item.readings!["so2_sub_index"]!.north ?? 0.0
aqm.so2_twenty_four_hourly = item.readings!["so2_twenty_four_hourly"]!.north ?? 0.0

Once the latest readings are converted to RegionalAQMs, they are saved, and the Map view’s @FetchRequest updates the map with the latest data via annotations. (I’ve yet to decide what I’ll do with the historical readings or what the retention policy will be.)


Viewing additional data related to the PSI reading can be accomplished by tapping on the annotations. When tapped a modal sheet is presented with all six data points and explanations of each reading. You can tap the (?) button2 to hide the explanations3.

Up Next

I am going to read-up a little bit more on using Core Data with SwiftUI make any necessary changes to the PersistenceController code. I also intend to add a Dock style menu to the bottom of the map with a refresh button, settings, and latest readings timestamp.

  1. AQM: Air Quality Measurement ↩︎

  2. This provides a nice example of how the @AppStorage property wrapper works. ↩︎

  3. Some definitions come from Singapore’s National Environment Agency, while others come from the U.S.A’s Environmental Protection Agency. ↩︎

Singapore Rail v2021.1

Permalink - Posted on 2021-01-01 03:00

Happy New Year! 🎉

Today I’ve released Singapore Rail v2021.1.

It’s a minor release with the following changes:

User Interface

  • Modernised the UI to use inset table views.
  • Notification configuration toggles are now tinted to match the rail lines.
  • UI elements now use system standard symbols.

New Features

  • The app icons are now available in an iMessage sticker pack.


  • Version numbers now follow a {year}.{month} convention.

Brexit Agreement Mentions Technologies from a Bygone Era

Permalink - Posted on 2020-12-29 14:21, modified at 14:50

Apparently, the Brexit Agreement has been in negotiation a lot longer than we’ve been led to believe because it references technologies that were introduced in 1997 and haven’t been updated since 2002. On page 921 of the agreement:

s/MIME functionality is built into the vast majority of modern e-mail software packages including Outlook, Mozilla Mail as well as Netscape Communicator 4.x and inter-operates among all major e- mail software packages.

Also, the now-defunct—since 2011—SHA-1 encryption algorithm shall be used:

the hash algorithm SHA-1 shall be applied

There’s no excuse for this appearing in a 2020 Trade Agreement. It’s embarrassing.

Permalinks and Other Updates

Permalink - Posted on 2020-12-28 13:00, modified on 2020-12-29 14:50

As I get up-and-running with Hugo I am making some small tweaks as I go.

  • I’ve updated the article permalink structure for the site: they will now appear under a /:year/:month:/{slug} structure. As a result—and I apologise—some of the older posts on the site may reappear in RSS readers as the <guid> used in the feed was based on the permalink. All future posts use a UUID or MD5 for the the RSS <guid> or JSON "id"1.

  • The RSS (/index.xml) now includes the full content of each article and will use post identifiers for newer articles instead of the permalink. (The JSON feed (/index.json) was already set up for full content and post identifiers.)

  • I’ve settled on using Ideal Sans as the site’s font.

  • Custom typography and Bigfoot.js footnotes add a bit of weight to the site which can affect loading times. I’ve made some performance improvements to mitigate that where possible.

  • I’ve enabled privacy-focussed analytics from Plausible. (I’m on the fence with this one. It may disappear.)

  1. I’m not aware of a way to generate and include a UUID in an Hugo archetype. My solution is to use an MD5 hash of the date when creating new content via hugo new posts/new-post.md. ↩︎

Singapore Breathe — Development Diary #1

Permalink - Posted on 2020-12-26 13:00, modified on 2020-12-28 00:00

This post is part of the Singapore Breathe Development Diary Series.

I’m starting work on a small app that will be part of my Singapore App Series. It’ll be called Singapore Breathe and it’s an air quality monitor that pulls data—Pollutant Standards Index (PSI) and (hourly) Particulate Matter (PM2.5)—from the National Environment Agency’s API1.

It’ll be available on Apple Watch and iOS devices. (It’ll also be available on macOS, but only because I won’t be enabling that restriction on the App Store.) Additionally, Singapore Breathe will be written in SwiftUI and the core portions will be open source2.

The GitHub repository is here and I’ve done some (very) early work on the models and decoding tests.

My aim is to publish new development diaries every week or so.

  1. Helpfully, this API doesn’t require any authentication. ↩︎

  2. I am thinking of adding push notifications in the future which would alert users when the air quality reaches unhealthy levels during haze season. However, push notifications will require a server and API keys. Those items will not be open sourced. ↩︎

Swift Package Manager: Binary Target Appears Many, Many Times

Permalink - Posted on 2020-12-21 11:18, modified on 2020-12-28 00:00

An interesting problem appeared in the first internal build of NetNewsWire 6: the app size was much bigger—almost 4x bigger—than NetNewsWire 5. On investigation, the Sparkle framework—now included in the app as a Binary Target via a Swift Package—was appearing in multiple places in the archived package.

Ideally, Xcode should only include the Sparkle framework in the Frameworks folder of the package. In the case of NetNewsWire 6, it appeared in Frameworks, PlugIns, and XPCServices, resulting in a 25% increase to the app’s install size.

It turns out that this behaviour is a bug. Any time there is an Embed phase in Build Phases, the framework is copied to that folder:

  • Embed Frameworks: Sparkle was copied to the Frameworks folder
  • Embed XPC Services: Sparkle was copied to the XPC Services folder
  • Embed App Extensions: Sparkle was copied to the PlugIns folder

The current workaround is to remove the Sparkle framework using a Build Phase script. See the below example for NetNewsWire:

find "${PLUGINS_DIR}" -name Sparkle* -execdir rm -rf {} \;
find "${XPC_DIR}" -name Sparkle* -execdir rm -rf {} \;

The bug on the Swift bug tracker really ought to be rated higher than Medium: this behaviour will result in rejections from the App Store.

A Website for 2021

Permalink - Posted on 2020-12-19 14:00, modified on 2020-12-28 00:00

Minor Update: I’ve refreshed my website for 2021 (a few days early). It’s now powered by Hugo and is a fair bit faster than the Ghost-powered blog I’ve been using for most of 2020. It’s easy to forget just how much faster static sites are.

Everything should work as it did before as I’m using Netlify’s fantastic _redirects file to point old URLs to the right place. For example, /feed.json will now redirect to /index.json.

I’ve ditched comments and the whole newsletter idea. I simply don’t write often enough.

Beyond that, nothing else has changed.