Browse through the showcased feeds, or enter a feed URL below.
Permalink - Posted on 2021-01-08 12:05, modified on 2021-01-09 22:01
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.setToRecipients(["mail@test.com"])
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.
Permalink - Posted on 2021-01-06 00:24, modified on 2021-01-09 22:01
Podcasts saying literally nothing for 20 minutes. pic.twitter.com/nmfka1Gjsp
— Jonathan Ogden (@jogdenUK) January 5, 2021
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.
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:
README
has been addedThere are two APIs that I’m using in Singapore Breathe:
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
.
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 RegionalAQM
1 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 RegionalAQM
s, 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.
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.
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:
{year}.{month}
convention.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.
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.)
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
. ↩︎
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.
Helpfully, this API doesn’t require any authentication. ↩︎
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. ↩︎
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:
The current workaround is to remove the Sparkle framework using a Build Phase script. See the below example for NetNewsWire:
PLUGINS_DIR="${CODESIGNING_FOLDER_PATH}/Contents/PlugIns"
XPC_DIR="${CODESIGNING_FOLDER_PATH}/Contents/XPCServices"
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.
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.