Devlog: March 3, 2023

I’m starting on iPad support for ScreenCred. It may not be quite as difficult as I originally thought. It doesn’t look too bad out of the box, but it just feels like a giant iPhone app. That might be fine for now. It’s pretty simple, so not sure if really diving into making it a stellar iPad app is worth it.

I also tried enabling macOS support, but that is going to require a lot more work. Again, not sure if it’s worth it. But I will explore it a bit more. Need to research some more if there are better ways to make cross-platform apps without a ton of #if os(iOS) and whatnot.

screencred devlog

Devlog: February 24, 2023

Finally got a couple things done on ScreenCred. Fixed a couple bugs that were…bugging me.

I updated the search sorting a bit. I was finding TMDb’s sorting of results a bit odd. I previously updated it to sort by popularity. Now, if a title matches the query exactly, it will be at the top. I also prioritize matches that contain the exact text of the query. Other than that, still sorts by priority.

I was also having a bug where the toolbar would think the keyboard was still there after dismissing search. This only happened on device, not simulator. For now I put in a little hack to dismiss the keyboard manually before dismissing the sheet. Seems to be related to using .searchable, which does not give access to the focus state of the search field—as far as I know.

With those 2 pesky bugs off my plate, I think I will start on iPad support next week.

screencred swiftui devlog

Devlog: February 22, 2023

I’ve lost my groove. Been a busy week, so I haven’t been able to work on anything. I was going to work on things today, but then some urgent things came up at my day job.

But honestly, not the worst thing. I need a little bit of time to breathe, figure out my priorities, and do a bit of planning.

Speaking of planning, I’ve been trying a new system: index cards. I was getting overwhelmed with task apps. It’s so easy to just put a bunch of things in and have a never ending list. So, now, I’m trying index cards. When I’m planning a feature, I make sure everything fits onto an index card. If it doesn’t fit, it needs to be rethought, reprioritized, or broken up. This helps me make sure my backlog doesn’t get too long. Physically limiting tasks also helps prevent scope creep—it better be important if it’s going to take up space on this card. It seems to be a good system so far. I’m going to keep trying it out and tweaking it.

planning devlog

Devlog: February 17, 2023

Got too many things going on right now. Trying to do some redesigns to ScreenCred, but not feeling very inspired. So thought I’d switch to trying and making my own node server to generate the og:image. Netlify has been taking like 10 seconds, which is not great. I think what I came up with should run around ~600ms. Which is still not as fast as I would like, but better than 10 seconds! I’m trying to deploy it to, but that relies pretty heavily on Docker, and I am not even a novice when it comes to Docker. So maybe I should just run in on my Linode. IDK.

screencred devlog

Devlog: February 14, 2023

I woke up around 2am and couldn’t sleep. So decided to finish up some work on adding an App Clip to ScreenCred. Since the app is fairly limited in functionality, pretty much all of it can be included in the App Clip. I wanted to do this to go along with sharing links. If you share a link with someone who does not have the app installed, they should see the App Clip card in messages.

The docs for App Clips are actually pretty good. You have to click around a lot to find everything, but I was able to find 99% of what I needed in the official docs. I think I have this all setup correctly—and I’ve tested it as best I can on TestFlight—but kinda seems like you can’t be 100% sure until the app is released.

But, I think it’s mostly working. The cards in iMessage now have an open” button on them!

An iMessage card showing a link to the ScreenCred app, with a button to open in the appPretty button!

The App Clip will respond to the same URLs that I setup for universal links. The main difference between the app and the App Clip is how they handle the URL. In SwiftUI, the app uses .onOpenUrl, but the App Clip uses .onContinueUserActivity:

.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
    guard let url = activity.webpageURL else { return }

It took me a bit to find that NSUserActivityTypeBrowsingWeb is what you want to pass in as the string1.

I’ve only seen one or two App Clips out in the wild, so not totally sure how they are supposed to work. I guess I could read some human interface guidelines and whatnot, but I kinda feel like wingin’ it.

I think I need to give my brain a break for a few days. I’ve been really excited about working on ScreenCred, but it’s been taking over a bit. Need to slow down a bit. I just get so obsessive when I’m learning new technologies like universal links and App Clips, that it’s hard for me to stop until it’s all done. The goal of this is to learn how to make an app in a sustainable way, and I’m starting to see the signs that I’m moving too quickly.

  1. Would that be so hard to put right in the docs?↩︎

screencred app clip swiftui devlog

Devlog: February 11, 2023

I went down the rabbit-hole. I got an idea in my head that I want my app to have a share feature. Send a link to a friend, and they can open it in the app and load the results automatically. I’ve never done anything like that, so seemed like a cool opportunity to try something new. It got a bit more involved than I originally thought…

The first step was associating my domain with the iOS app. I spun up a simple 11ty site and created ./well-known/apple-app-site-association. It’s a JSON file that defines which urls your app responds to. Right now, I only want one:

  "applinks": {
    "details": [
        "appIDs": [
        "components": [
            "/": "/search/?*",
            "comment": "Matches any URL with a path that starts with /search/someid."

I technically have two apps—one for local development with .dev on the bundle id, and the other real one. So it’s nice you can associate multiple bundle ids. What to put in this file is not well documented, and Apple does not have a tool to validate it. I did find this validator.

I eventually got all this working, but had quite a bit of trouble.

  • Make sure your JSON is formatted correctly 🤦🏻‍♂️
  • Make sure the Content-Type is application/json
    • I’m not 100% sure this is necessary, but the validator I was using called it out. I needed to add a config in my Netlify config to make this file have the correct content type since it does not have a file type.
  • Apple caches apple-app-site-association. So changes may not be immediate. Seems to update about once and hour?
  • To bypass the cache when developing, add ?mode=developer to your url under Associated Domains:
  • But, don’t leave ?mode=developer in for TestFlight or AppStore versions.

This got me to the point that my universal links opened the app in the simulator! That was pretty exciting! To get them to work on an actual device, you need to open Settings > Developer and enabled Associated Domains Development under Universal Links. There’s also a diagnostics that you put in your domain and it will tell you if you have an app installed that will open links from that domain. It works when the dev app is installed, but for some reason, not for TestFlight versions 🤷🏻‍♂️.

In SwiftUI, you just have to use .onOpenUrl. Since I’m only handling one url, the logic is simple. I check that the URL has a valid search request, and then set it up in the app. As part of this, I used a RegexBuilder! I didn’t have to, but wanted to try it out. Pro tip, you need to import RegexBuilder.

import RegexBuilder
let regex = Regex {
    TryCapture {
        ChoiceOf {
    } transform: {
    Capture (
let matches = url.path().matches(of: regex)
guard matches.count == 2 else { return }
let (_, firstType, firstId) = matches[0].output
let (_, secondType, secondId) = matches[1].output
// Fetch data with this information

Generating Landing Page

Now, what if someone does not have the app installed? They should be taken to a somewhat helpful webpage. This part would be pretty simple if I had a server-rendered page, or even a single-page app. But I make things hard for myself and I don’t have that. I have a statically generated 11ty site.

My search urls have the path /search/t123456m7890. That last part is the type and id of 2 movies or shows. That can be anything. I can’t reasonably generate a webpage for each possible combination ahead of time. Enter Netlify On-Demand Builders.

11ty has support for this. So after several attempts, I was able to make a builder function that generates a page with different content based on the last part of that URL.

I got the page to generate, but I wasn’t sure how to get the data dynamically. I’m not an 11ty expert, but the whole point is to have a data ahead of time. I don’t have that. This article cleverly uses a async filter to fetch the data. So pretty much the same thing as the iOS app, I take the search param, validate, parse, and fetch the data. Using that data, I can display the poster images and names of the selected movies or shows.

Screenshot of a website showing the poster image of Star Wars: Andor and a partial poster of Rogue One: A Star Wars StoryI was moving fast, I'll make this page look better later. I promise.

Awesome! Safari will also show that button to open it in the app. That’s pretty cool. But, when you share a link in messages, you just get a boring link. It needs some pizazz.

Generating og:image

iMessage uses the meta og:image to add an image to a link. So since these pages are dynamic, I need to also dynamically generate the og:image as well.

I mostly followed this guide on how to do exactly that with Netlify functions. It worked perfectly locally, but when I deployed to Netlify, I had a lot of issues. Basically it came down to using @sparticuz/chromium instead of chrome-aws-lambda. It’s beyond me what the differences are, but came across the solution on the Netlify support forums.

Aside from those issues, it’s fairly simple. Create and HTML file with some CSS. I did have to change the page.setContent to waitUntil: "networkidle0" instead of "domcontentloaded". I’m sure that makes it run longer, but the poster images wouldn’t load otherwise.

So I’m currently using:

"@sparticuz/chromium": "^107.0.0",
"puppeteer-core": "^19.1.1"

And that works for now…

It’s a bit slow, but IMO, worth the wait:

“Screenshot showing iMessage url previews”😍

I still have some wrinkles to iron out. I guess the pages don’t always generate quickly enough or something because when you open the share sheet, you don’t always see the og:image…but sometimes you do…so I’m not sure. I’ll see what I can do.

I guess at this point it’s a little hard to hide the fact that the name of the App is ScreenCred.

Anyway, that was my adventure of the past couple of days. Was pretty fun and I hope it all keeps working!

screencred ios 11ty serverless devlog

Picture of Sam Warnick

As my daughter says, I'm just a tired dad, with a tired name, Sam Warnick. I'm a software developer in Beaufort, SC.

Some things I do