• 2021-9

    We are coming close to 10 consecutive weekly posts.


    I’m continuing the work on Loc. This project distracted my from finalizing my local-notification library, but I’m not too worried. Loc has a tiny scope and could be feature-complete soon. I will just come back to it.

    As I’m mainly focusing on Swift/iOS these days, I felt the need to keep my Rust knowledge fresh in my head. For this, I started following this tutorial: Hecto: Build your own text editor in Rust. I was aware of all concepts touched so far1 but I’m happy to reapply them.


    I managed to write a small blog posts about releasing private pods using GitHub actions. I had this note for a while, and I’m happy I finally wrote it down as a post. Let’s hope it doesn’t take another year until I write down one of my many other notes.


    Midnight in Chernobyl: The Untold Story of the World’s Greatest Nuclear Disaster by Adam Higginbotham: Again, this books fits right in the era I’m currently reading about. After watching a few documentaries about the Chernobyl disaster, I was already aware about many details concerning the explosion and the fallout. This book goes even deeper by following the personal lives of people involved, and looking closely at the political landscape of that time. Full recommendation.


    This week I discovered LanguageTool. It’s an open-source grammar, style, and spell checker. A perfect replacement for grammarly that always felt a little creepy. LanguageTool also supports many more languages.


    1. I’m in the middle of chapter 3 

  • Release private Pods using GitHub Actions

    Another example of: “let’s make a blogpost out of this cobbled together note, so I don’t have to figure it out in a year again”.

    Using CocoaPods’ private Pods allows you to modularize your Application while hosting the code in different private repositories1. Releasing a new version of your private Pod can be time-consuming and is therefore a perfect opportunity for automation, in this case using GitHub Actions. This workflow assumes that you already created your private spec repo and successfully pushed an initial version of your Pod.

    I substitued all variables with <>. Example of the file with actual values: Gist to release.yml

    When to trigger the workflow

    You first need to decide on when to trigger your automation workflow. In this example I decided to use the workflow dispatch trigger. This gives the engineer the freedom on when to trigger a new release while, providing additional information like the semantic version to be used or a name for the release2. We start our release.yml with the following:

    name: Release
            description: 'Version  (i.e. 1.1.3)'
            required: true
            description: 'Name of the Version'
            required: true

    Setup the job

    We now need to decide and what machine to run our automation and install the required tools:

        runs-on: macos-latest
        - uses: actions/checkout@v2
            fetch-depth: 1
        - name: Bundle Install
          run: bundle install

    We run the job on hosted Macs provided by GitHub actions macos-latest, as we require Xcode to release a new version of our module. We assume a Gemfile exists in the root of the repository that defines the dependency on gem cocoapods. (To automatically bump the .podspec-version, we also require gem "fastlane"). We install the tools using Bundle install.

    Add the private spec repo to CocoaPods

    We need to make the CocoaPods installation of the Action Runner aware that our private spec repo exists and give it the rights to modify this repo:

        - name: Add Pod Spec
          run: pod repo add <NAME_OF_REPO> https://<GIT_ACTOR>:<GIT_TOKEN>@github.com/<ORG>/<REPO_NAME>.git

    We are passing the username and personal access token of a user with write permissions to the spec repo along the pod repo add command. I suggest defining the username and the access token as encrypted secrets.

    Optional: Bump the Podspec version

    We can use the version parameter passed along the workflow_dispatch trigger and a fastlane action to automatically update the podspec version of the dependency. We commit these changes and automatically open a PR with it.

        - name: Bump Podspec version
          run: fastlane run version_bump_podspec path:"<NAME_OF_POD>.podspec" version_number:<VERSION>
        - name: Create Pull Request
          uses: peter-evans/create-pull-request@v3
            title: Release/<VERSION>
            base: main
            branch: Release/<VERSION>
            body: |
              Release: <VERSION>
              - Bump version to <VERSION>

    Create a release tag and push the new version

    As a last step, we create a release on GitHub3 and push the new version of our pod to the spec repo:

        - name: Create Release
          uses: actions/create-release@v1
            commitish: Release/<VERSION>
            tag_name: "<VERSION>"
            release_name: "<VERSION>"
            body: "<VERSION>"
            commit-message: "Bump pod version"
        - name: Push Pod Spec
          run: pod repo push <NAME_OF_REPO> <NAME_OF_POD>.podspec

    Run the action

    You can now run the action using GitHub’s UI, as outlined again here.


    1. The necessity of hosting your code in different repositories and the upsides/downside this brings is a whole different topic. Not even talking about the choice of dependency manager here. 

    2. The name could be used to automatically create a new release on a CHANGELOG file or give the created GitHub Release more context. 

    3. This automatically creates a git tag. We use the version number and name provided by the trigger to populate the title/body of the release. 

  • 2021-8

    It’s time for another short one.


    Almost all features I wanted for Loc, my privacy-focused location tracker, are now implemented. I already use it every day. Mainly to discover the days I went for a walk or bought groceries from the stores. Oh well… As it already has a badly designed App-Icon, I’m wondering if I should consider submitting it to the App Store, or just have it open-source.


    Nothing to Envy: Ordinary Lives in North Korea by Barbara Demick: I’m currently gravitating towards books focusing on the post-WW2-era, the cold-war, and its fallouts. This book about North Korea fits right in there. It leaves no space for illusions how life in this dictatorship looks like. Brutal and eye-opening. Full recommendation.


    WandaVision remains the only new thing we are currently watching on TV. I’m sad that it will be over soon. As it’s available on Netflix, we started a re-watch of New Girl 🤷‍♂️


  • 2021-7

    The past week I finished reading the 5th book this year. Without an exception, all of them have been audiobooks. For over a decade now I’m listening to a lot of audio content. It started with public radio shows and documentaries in the car or iTunes, followed by tech podcasts focused on Apple and their satellites (Build and Analyze, Hypercritical, You Look Nice Today). I still consume a ton of podcasts of their like but mainly for the people in it. For learning and knowledge, I more and more choose audiobooks. They are written and researched over years and can afford to go into great detail. An audiobook with a 30 hours playtime is no exception. I’m deleting podcast episodes left and right to give time for audiobooks. A handful of podcasts I never miss or delete is still left: Accidental Tech Podcast, The Unmade Podcast, BONANZA, Game Show! from the Incomparable, My Brother, My Brother And Me. No deeper meaning here, just an interesting change of behavior.


    Arq 7 go released, reintroducing a new, fully native UI for macOS. Arq 6 used an Electron UI and was disliked by many people. Even though I never felt the need to complain about it, I’m still happy to see a new and shiny native UI for my backup tool of choice. 1

    By accident, I stumbled over the early preview for Bear’s new Markdown editor: Panda. It looks incredibly promising. I still dream about having a great-looking and powerful markdown-editor with real-time formatting, working with plain text files, and note/attachment linking. The new version of Bear will most likely not be that but it’s still a huge step in the right direction.


    I continued working on my local notification helper library. Soon, I should be able to start testing it in a real App. I hope to write a dedicated blog-post for it once it reaches 1.0.

    A tiny other side-project popped up: Loc. A privacy-focused location tracker App. Using Apple’s startMonitoringSignificantLocationChanges callbacks, this App will store the location with a timestamp in a local database. That’s it. No 3rd-party libraries, no trackers, no network requests. It’s a super early WIP but I already have a running version on my phone.


    Surely You’re Joking, Mr. Feynman by Richard Feynman: I enjoyed this book a lot. It has problematic paragraphs, especially reading it in 2021, but it gives great insights into a hugely interesting life of a brilliant man.


    1. I’m backing up all machines in the house to Backblaze B2 and a local, big disk attached to an always-on Mac Mini. 

  • 2021-6

    Does every post need an intro? I don’t think so.


    It’s now almost half a year I migrated away from Alfred as my macOS launcher App. I was using Alfred continuously since 2012, it’s was usually the second App I installed on every Mac1. As my workflows and configuration grew over time, syncing2 the configuration and therefore using the App became less reliable. For such a fundamental tool, it only takes a few failures for you to lose trust in it. I tried slimming down my configuration, but since day 1, I needed to configure Alfred a lot to fit my needs, even though 99% of all actions are simple openings and file modifications. I revisited LaunchBar and it clicked right in place. Almost all of the default configurations work for me. I just needed to add a few text snippets I use daily, otherwise, there is nothing else to “sync” between Macs. It’s fast and solid as a rock. I no longer need to actively think about my App launcher!


    I continued working on my local notification iOS Framework Nocally. I’m building it as a SwiftPM package and wanted to add a basic example app to the repo to start testing the first assumptions. The steps needed to achieve a similar result as CocoaPod’s development pods are kind of funny: Source I’m incredibly happy SwiftPM finally exists and can be used from inside Xcode. I’m also optimistic that it will become the default way to handle dependencies in Xcode projects. It just might take a little while longer until we reach that point.


    No book finished this week but I started listening to “Surely You’re Joking, Mr. Feynman”. Almost halfway through, I should finish it just in time for next week’s post. I also started reading “A Philosophy of Software Design” by John Ousterhout. No opinion yet!


    1. The first App was 1Password, but this also changed. More on that in a different post 

    2. I was using iCloud Drive to sync the configuration file. Every few days, the file got deleted from the hard-drive and I needed to manually redownload it. I never figured out why. All Optimise Mac Storage toggles were disabled. 

  • 2021-5

    This week’s post will be a very short one. The last week just flew by, with busy times at work, the continuation of the lockdown, and the endless Berlin gray weather. At least now something changed, it snowed and it’s freezing cold.


    I mentioned my ongoing refactoring efforts for a very old App of mine in the previous post. This work continued and I finally started tackling the biggest pain point this App has: Scheduling local notifications functioning as reminders. The native APIs to schedule those notifications are lacking capabilities and require custom workarounds, for example:

    Schedule local notification 
    to remind me every second day, 
    at 19:00, 
    starting from tomorrow

    The current implementation offers many capabilities already but is not as robust and well-tested as I would like. Therefore, I started extracting and reimplementing this logic inside a SwiftPM packages. The code will be open-source here.


    Agent Sonya by Ben MacIntyre: Another non-fictional spy story. This time about an agent working mainly before and during World War 2. Incredible, again!


  • 2021-4

    I discovered r/wallstreetbets quite a while ago browsing through the suggested subreddits on Apollo and loosely followed it every since. Seeing their often funny/idiotic ideas play out this week wasn’t really on my 2021 short-list. Big financial news outlets reporting about stonks and tendies felt even more impossible. Let’s see in a week if the squeeze will haven been squozen.


    I’m currently refactoring/modernising one of the very first Apps I ever built and shipped. Many code paths were untouched for almost 10 years. We rarely think about code of something that will exist in the current state / untouched for a decade. The code wasn’t beautiful / DRY / fully unit-tested but it continuously provided value for many users. Somewhere in here is an idea for a standalone blog post. 🤔

    I’m reworking the whole UI layer in SwiftUI and just wanted to share two posts I enjoyed this week:


    Even though I’m no longer reading Twitter using a native client (RSS all the way), the release of Tweetbot 6 got me excited. For me, Tweetbot in every version and era has been a prime example of how an iOS App should function/look and feel. The same is true for the 6th version. Beautiful!


    The Spy and the Traitor by Ben MacIntyre: I never read fictional spy stories, but it’s hard to imagine they could be more thrilling than the real-life events depicted here.


    We revisited Star Wars: The Force Awakens yesterday. Even though I enjoyed the movie when it was released, I’m no longer sure it holds up for me. It’s hard to appreciate it standalone, knowing what happens in the consecutive movies.


  • 2021-3

    One small learning for the week: Substack isn’t for me. I tried finding newsletters/content I enjoyed and mostly failed. I also don’t like reading in my mail inbox. Conceptually my mail App is closer to a ToDo-list; things I have to act on until I can archive it. It is not the place to read a well-structured blog post. The hostile-ness of tracking users in their mail App is a whole other topic. I already deleted my publication and am now trying to delete my whole account as well.


    After John Gruber’s recommendation on Daring Fireball, I also installed HUSH. It’s a small Safari extension for iOS and macOS that blocks cookie popups and trackers. I was happy to see that the whole App is open source and is not integrating any analytics or data collection schemes. This small extension in combination with my local Pi-Hole instance greatly improves my web-browsing experience. Just imagine opening a news website without trying to find the button to opt-out tracking in the inevitable huge pop-up.


    Even though I’m in a little physical reading slump and struggle to finish the two books I’m currently reading, I managed to listen to two audiobooks and started a third.

    The Infinite Game by Simon Sinek: An inspirational book about business; I liked many of the ideas portrayed and enjoyed a few of the many examples given, but it gets repetitive after a while.

    Man’s Search for Meaning by Viktor E. Frankl: A first-hand account of life inside Nazi concentration camps combined with an introduction to Logotherapy. I can’t even describe how much I enjoyed this book. I will need to revisit it again but it has the potential to be one of my all-time favorites.


    One more week with Apple Music and I’m still happy about the recommendation quality. Besides introductions to new artists, I frequently discover music that I missed from artists I enjoy. This weeks example: Daughter - His Young Heart. This recommendation also made me discover that the voice behind Ex:Re and Daughter is the same person.


  • 2021-2

    Week 2!

    For the past days, I noticed a new behavior in my casual phone usage: I was reading comments and @-mentions of everything. Tweets, posts on news.ycombinator, Reddit. I was actively looking for opinions and statements I strongly disagree with. I even went as far as reading r/conservative. I never had the intention or did reply to anything, just reading to feel outraged. This has to be a definition of doom-scrolling. I wasted a lot of spare time that I would have rather used to build.

    AirPods Max

    A few more words about the AirPods Max as they now accompanied me through the first work week with many Google Meets calls and hours of music listening: They are still everything I wanted them to be! Heads are different but I feel no discomfort wearing them 8+ hours a day. The sound quality is brilliant, the OS integration is way snappier compared to my AirPods 2nd Gen. The noise cancellation is pleasant, and switching to transparency mode is a joy.

    The case is annoying, yes. I still put them inside every night to trigger the low energy standby, even though the real benefits of doing this are not that clear. Sooner or later I plan to replace it with a 3rd party alternative but I’m not planning to spend 100 bucks on it.

    I’m slightly worried about the longevity of the headband mesh but we have to wait and see.

    So far not a single regret, would buy again.


    The developers behind Zola, my favorite static site generator in Rust, released v0.13.0. It includes YAML frontmatter support and therefore brings compatibility with content written for other systems, including Jekyll. This site runs on Jekyll and I’m now actively working on migrating it to Zola. I’m also using Zola in my mywe.blog project (more on that in the coming weeks) and already switched to YAML frontmatter. The content you create should always be easy to migrate.


    I rewatched Arrival after seeing it at the cinema in 2016. It holds up as one of my all-time favorite movies. I noticed two things:

    The song “On the Nature of Daylight” by Max Richter is used beautifully. I’m a big Max Richter fan after discovering him at the end of last year but never connected him to Arrival.

    Seeing the global news coverage of the event in the movie reminded me about the covid pandemic. I think besides 9/11, covid is the only true second global event in my lifetime. The landing of friendly heptapods would be the third, for sure.


    I bought my AirPods Max from a German electronics store chain as they were back-ordered everywhere else for months. For some reason, they included another 3 month trial of Apple Music. As we have been a Spotify family for years, I’m giving Apple Music another try. So far the recommendations are on point and I already have discovered many new artists and albums. I’m impressed and plan to revisit this topic in the coming weeks.

    Until then!

  • 2021-1

    Weekly recap format, let‘s go! I tried it before; end of 2017 / beginning of 2018. More or less a lifetime ago, counting in recent events. I loved writing these posts as they focused on things I did, learned, and enjoyed over the course of a week. Easy! Even now, I still use them as a reference for when I purchased an App or discovered a music artist. I also plan to cross-post these reviews to [Substack](https://hartlco.substack.com(, just for understanding purposes.

    Upcoming topics

    Thoughts on Apple and software development, coffee, music, books, cars, things I like! I also want to highlight what I‘m currently working on, either in my jobby-job or spare time.

    Mini Topic - AirPods Max

    I didn‘t write anything for the first week, just a few lines about the AirPods Max I bought on Saturday.

    The AirPods Max intrigued me the moment they were announced. I know the pricing and the case are ridiculous but they are still coming at a perfect time for me. I don‘t enjoy wearing big over-ear headphones when I‘m out. I feel isolated from the outside world. AirPods are perfect for this use case, easy to pop in and out, comparably subtle. But guess who is not spending much time outside these days?… I also used my AirPods a ton at home. Podcasts, Music, Google Meet/Zoom calls. Constantly switching between my Mac, iPad, and iPhone, rotating 2 pairs to charge them. Sometimes wearing them for 10 hours a day. Even though I don‘t mind having something stuck inside my ear canal, it starts becoming uncomfortable after a few hours. AirPods Max promise to solve the battery and comfort issues while keeping the effortless device switching and tight OS integration. For now, I just used them for a few hours to listen to music/podcasts and watch a few videos. This week will be the first work test. I plan to cover the AirPods Max in detail in an upcoming post.

    See you in a week.

  • I recently bought a Fuji X100V after shooting years on iPhones only. I almost couldn’t believe how good and fun this camera is. 4DC40DF5-CC45-4454-A7B6-E97D9EF55A16.jpg 174B94E5-B4CA-4D8C-990C-43988BEF08CD.jpg 0F3D22FA-4B55-46F0-AD30-D467978B67D8.jpg

  • Thank you all so much for the kind words and congratulations! It means so much to us! 🎉😭🥳

  • I was lucky, she said “Yes”! 2D5D36D2-5F7A-4D3A-98CC-58D38A7ECF2A.jpg

  • Unrelated feelings about the whole Hey situation:

    I have 0 interest in another proprietary E-Mail host/App.

    Own your domain and use public protocols.

    App Store guidelines need to be just and mutual beneficial. The playing field should be as even as possible.

    Side-loading of notarized Apps could drive new innovation on many areas.

    Apple, why are you fighting this battle now?

  • CI and TestFlight deployments using GitHub Actions

    A typical post of the category: “Better write a blog post about it, otherwise I will forget how to make it work”

    Early on in the development of Icro I integrated fastlane to run continuous integration tests on bitrise and build and uploaded release version from a local Mac. As GitHub Actions is now available and offers similar features as bitrise for open source projects, I decided to move the CI tests over and also start using it for Apple TestFlight deployments.


    Thanks to the already defined lanes, the migration to run unit tests on every push and pull request was trivially easy.

    The Fastfile just uses the run_tests action configured with the correct workspace and scheme:

    platform :ios do
      desc "Run unit tests"
      lane :tests do
        run_tests(workspace: "Icro.xcworkspace",
                  derived_data_path: "derivedData",
                  devices: ["iPhone Xs"],
                  scheme: "Icro")

    The GitHub action workflow file defines when the action should be run, installs the bundle dependencies (including fastlane) and runs the lane

    name: CI
    on: [push, pull_request]
        runs-on: macos-latest
        - uses: actions/checkout@v1
        - name: submodules-init
          uses: snickerbockers/submodules-init@v4
        - name: Bundle Install
          run: bundle install
        - name: Build and test
          run: bundle exec fastlane tests

    TestFlight deployment

    Running unit tests usually does not require complicated code-signing steps or access to shared accounts, and passwords. All this is required to deploy builds to TestFlight. As a first step, the certificates and provisioning profiles need to be made accessible for GitHub actions. Luckily fastlane also helps with this: match. Following the guide to integrate fastlane match, I created a repository accessible for GitHub Actions to store encrypted profiles and certificates. The passphrase for the certificates is saved inside GitHub Secrets. Fastlane match will then install the certificates in the keychain of the Action runner to successfully sign the build. The added lane to the Fastfile looks like this:

    lane :beta_ci do
          name: "Fastlane_CI",
          password: "CI_Password",
          default_keychain: true,
          unlock: true,
          timeout: 3600,
          add_to_search_list: true,
        match(type: "development", readonly: true, keychain_name: 'Fastlane_CI', keychain_password: 'CI_Password')
        match(type: "appstore", readonly: true, keychain_name: 'Fastlane_CI', keychain_password: 'CI_Password')
        build_app(workspace: "Icro.xcworkspace", scheme: "Icro")
        upload_to_testflight(skip_waiting_for_build_processing: true, username: "icro@hartl.co")

    bump_version is just a simple internal lane to set the build number to the number of commits on master. We need to call create_keychain to use a specialised keychain on the Action runner. Otherwise, the build would be blocked forever, during the signing, a keychain-popup to access the installed certificates would block the execution. Two match actions will install the certificates for development and appstore to ensure a successful signing. Using a restricted App Store Connect user, the build gets uploaded to TestFlight. The Fastfile does not have access the defined GitHub secrets. Therefore the passphrase for match and the password for the App Store Connect account are defined as environment variables on the deployment workflow file.

    name: Deploy
        types: [published]
        runs-on: macos-latest
        - uses: actions/checkout@v1
        - name: submodules-init
          uses: snickerbockers/submodules-init@v4
        - name: Bundle Install
          run: bundle install
        - name: Set Appstore Connect User
          run: bundle exec fastlane fastlane-credentials add --username icro@hartl.co --password $
        - name: Fastlane Beta CI
          run: bundle exec fastlane beta_ci
            MATCH_PASSWORD: $
            FASTLANE_PASSWORD: $

    The deployment is triggered on every newly created release inside GitHub (just a tag). The workflow will install the bundles again, set the fastlane credentials by accessing the APPSTORE_PASSWORD inside GitHub secrets. As a final step, the lane gets called with the match passphrase and App Store Connect password defined.

    With this setup in place, every push and pull request to Icro will run unit tests, every new release will automatically create and upload a new build to App Store Connect.