Default Apps 2023

I’m so in the Apple ecosystem, this is almost not even worth posting 😅

📨 Mail Client: Mail.app
📮 Mail Server: iCloud, Gmail
📝 Notes: Apple Notes, Byword
✅ To-Do: Things, Apple Reminders
📷 Photo Shooting: iPhone 15 Pro
🎨 Photo Editing: Apple Photos
📆 Calendar: Apple Calendar
📁 Cloud File Storage: iCloud
📖 RSS: Wire
🙍🏻‍♂️ Contacts: Apple Contacts
🌐 Browser: Safari, Firefox Developer Edition, Chrome (Work)
💬 Chat: Messages, Slack
🎵 Music: Apple Music
🎤 Podcasts: Apple Podcasts
🔐 Password Management: 1Password (but tempted to switch to Apple Passwords now that they support shared vaults)
🧑‍💻 Code Editor: vim, highly customized

It’s becoming quite common for web developer blogs that show up on the front page of Hacker News to not have an RSS feed. This feels like a step backwards. I know RSS usage is not what it once was, but it seems like something you should get for free. Blogging platforms like WordPress (currently 43% of all websites) and even Mastodon support RSS out of the box. The Next.js blog demo automatically builds an RSS feed. There are popular RSS feed generator packages for every language.

For example:

Support the open web and make reading your blog easier!

Hush WKWebView

If you’re building an iOS app with webviews, you need to decide whether to use WKWebView or SFSafariViewController. The WebView version gives you a lot more control, but you can’t use Safari Extensions. On the other hand, the Safari version automatically gets some of the features of Safari like content blocking extensions, but customization options are pretty limited and it doesn’t support loading local HTML files.

In this demo, I’m going to walk through a simple Cookie Consent blocker in WKWebView using the Hush block list, a Swift library from the AdGuard team, and the WKContentRuleListStore class that was added way back in iOS 11.

First, download the Hush block list and add it to your project.

Next, load the file as a [String] that is split on newlines.

guard let path = Bundle.main.path(forResource: "cookiemonster", ofType: "txt") else {
    return "[{\"trigger\": {\"url-filter\": \".*\",\"if-domain\": [\"domain.com\"]},\"action\":{\"type\": \"ignore-previous-rules\"}}]"
}

let file = URL(filePath: path)
guard let rules = try? String(contentsOf: file).components(separatedBy: .newlines) else {
    return "[{\"trigger\": {\"url-filter\": \".*\",\"if-domain\": [\"domain.com\"]},\"action\":{\"type\": \"ignore-previous-rules\"}}]"
}

The fallback here is just a rule that doesn’t block anything.

After that, convert this list into something that WKContentRuleListStore can use with the AdGuard library.

let convertedRules = ContentBlockerConverter().convertArray(rules: rules).converted

And finally, compile the blocklist and add it to the WKWebView configuration.

WKContentRuleListStore.default().compileContentRuleList(
    forIdentifier: "ContentBlockingRules",
    encodedContentRuleList: convertedRules) { (contentRuleList, error) in

        if let _ = error {
            return
        }

        if let contentRuleList = contentRuleList {
            webview.configuration.userContentController.add(contentRuleList)
        }

        let request = URLRequest(url: url)
        webview.load(request)
}

And to prove it works, here is a before and after from Stack Overflow.