Devlog: February 3, 2023

Today’s feature is sorting. Initially, I hardcoded it to sort by number of credits, most to least. Thought it might be nice to let you wort the other way, and by name. The trickiest part of figuring out what UI to have for option. Right now, the best place is in my more” menu. I went through a couple iterations though.

First

A screenshot of an iOS app showing a menu of 4 sort optionsWall sort options

This was my first take. My instinct is to lay all the sort options out so they are easily accessible. Generally, I think this is a good idea. But here, it felt too repetitive, cluttered and, in my opinion, didn’t give enough information of what the sort actually was. Maybe if I could think of better labels and icons, it would work better.

Second

Looking for inspiration, I came across the sort options in iA Writer.

A screenshot of iA writer showing a menu of sort options divided into 2 groups of values and directionsiA Writer is a good app and we should emulate good apps right?

I like that it divides what you can sort by from the direct. This also lets you give really descriptive labels for the sort directions 👍1. So I went in that direction.

A screenshot of an iOS app showing a menu of 4 sort options divided into 2 groups of values and directionsLook familiar?

I like it! But, it has one crucial flaw—changing what you sort by immediately closes the menu, so you have to open it up again to change the direction. Not a great experience.

Third

Okay. Too many taps. Can I make it less taps? What about more nested menus? Everybody likes that right?

A screen recording of an iOS app showing a menu with options to sortBetter?

You have to really drill in, but you can get exactly the sort you want without too much fuss. Which I like better.

Final?

Then it hit me, why am I putting the options under a submenu. So I put them in their own section at the top level of the menu. This is my favorite so far and what I’m sticking with. Lets me keep the descriptive sort direction labels, doesn’t take up too much space, and doesn’t require too many taps. Plus it has a cute like section header.

A screen recording of an iOS app showing a menu with options to sortJust right...hopefully

I didn’t know you could put a section header in a menu. Here’s the code if it’s helpful at all.

Menu {
    Section("Sort") {
        Menu {
            Picker("Number of Credits", selection: $creditSort) {
                Text("Most to Least")
                    .tag(CreditSort.numCredits(.descending))
              Text("Least to Most")
                    .tag(CreditSort.numCredits(.ascending))
            }
        } label: {
            if case .numCredits(_) = creditSort {
                Label("Number of Credits", systemImage: "checkmark.circle")
            } else {
                Text("Number of Credits")
            }
        }
        Menu {
            Picker("Number of Credits", selection: $creditSort) {
                Text("A to Z")
                    .tag(CreditSort.name(.ascending))
                  Text("Z to A")
                    .tag(CreditSort.name(.descending))
            }
        } label: {
            if case .name(_) = creditSort {
                Label("Name", systemImage: "checkmark.circle")
            } else {
                Text("Name")
            }
        }
    }
}

This might not be the simplest way of doing this, but it’s straightforward, at least to me. Had to do some trickery to add a checkmark to the menu item to show a child is selected. Might clean that up later.

It was fun to iterate on this and figure out all the different ways I could do this. There’s a bunch of options I didn’t explore at all. Going to let this sit for a few days and see how it feels.


  1. In general, ascending and descending mean almost nothing to be when it comes to sorting. I like it when it’s clear—A to Z, Z to A, etc.↩︎

Projects ScreenCred devlog

Devlog: February 2, 2023

My family has been sick for a bit, so I’ve been mostly working on taking care of them and trying to not get sick myself. Finally starting to get back to a bit of normalcy now.

Today I added a button.

🥳

It’s a details button next to a name. It will open the details of that person in The Movie Database. I don’t want to make a full TMDb client. So my current plan is to just link to their pages. A pretty straightforward

A screen recording of opening the details of Aidan Cook in The Movie DatabaseI don't like where the button is...

Along with that, I added a setting to open the details in the app or in Safari. The in-app browser uses SFSafariViewController. This is the one that comes with the done button, the open in Safari button, etc. I first tried WKWebView but that just displays a webpage with none of the other stuff. When the setting is enabled to open details in Safari, instead of using Link, I use openUrl environment variable. Pretty nifty.

struct DetailsLink: View {
    @AppStorage(SettingsKeys.openDetailsInBrowser) var openDetailsInBrowser = false
    @Environment(\.openURL) var openUrl
    
    var body: some View {
        Button {
            if openDetailsInBrowser {
                openUrl(url)
            } else {
                // Show 
            }
        } label: {
            Label("Details", systemImage: "info.circle")
        }
    }
}

I’m using @AppStorage for saving the setting. It is simple, but I’m not 100% sold on it yet. It requires a lot of duplication. If I want to use the setting in multiple places, I need to define a default in multiple places, which feels odd to me. Just found this article about an alternative that looks interesting. Going to give that a closer read.

Projects ScreenCred devlog

Devlog: January 25, 2023

I’ll be brief because kids are crying and we need to eat dinner soon.

I did a small revamp of history. I was storing each time a comparison ran, even if the two movies or shows were exactly the same. So first, I added a toggle to hide duplicates. Then I started on deletion. I wanted to show all items when you went to delete them. It started to get a bit unwieldy. So I removed that setting. Back to seeing all your comparisons. Then I decided, instead of adding a new entry each time, if there is an existing one, update the history item. Maybe I can add a counter for the number of times those two were compared or something.

Anyway, came full circle a couple times, but happy where I landed.

Did have a strange issue with EditMode where the environment was not being set how I would expect. I ended up having to declare my own EditMode state and then pass that as an environment to EditButton and my List. Otherwise, my History View had no idea if the list was editing or not.

struct History: View {
    @State var editMode: EditMode = .inactive
    
    var body: some View {
        List {
            ForEach(historyItems) { historyItem in
                ...
            }
            .onDelete { indexSet in
                ...
            }
        }
        .environment(\.editMode, $editMode)
        .navigationTitle("History")
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                EditButton()
                    .environment(\.editMode, $editMode)
            }
        }
    }
}

Projects ScreenCred devlog

Devlog: January 24, 2023

Went the ZStack route. Things seem to be working well now!

A screen recording showing images resize as the user scrolls. The images change size smoothly.Much better!

I also added the poster images behind the thick material background. I think this gives a cool colored effect when something is selected. I let the material do the magic so I don’t have to determine the dominant colors of an image.

I’m sure I will come across some edge cases I need to deal with, but I’m pretty happy with the effect.

Projects ScreenCred devlog

Devlog: January 23, 2023

Was kinda in a funk today. Took on a task that was maybe a bit beyond my energy level today. I’m trying to make the images shrink as you scroll up. I have a GeometryReader updating a PreferenceKey as you scroll. The problem is that the size adjustment changes the position of my measurement view in the scroll view. I think this is because my header is in a safeAreaInset. I may need to change my header to be in a ZStack or something.

A screen recording showing images resize as the user scrolls. The images do not change size smoothly.Not great...

I’m not quite sure what else to try. Using safeAreaInset seems to be the right choice for this layout, but obviously is causing issues. Even if I use the global coordinate space instead of a named/local one, the offset does not seem to change smoothly”. So probably going to need to try some different layouts.

Projects ScreenCred devlog

Devlog: January 18, 2023

I don’t know what got into me. Basically, I nerd sniped myself. I thought, sure would be nice if I could get a notification if it looks like it’s going to freeze tonight.” A somewhat reasonable thought. I could’ve looked for an existing solution, or cobbled together a Shortcut, or use some other use some other automation tool. But no. I had the bright idea to make my own solution and I mostly regret it.

My brain came up with this idea yesterday:

  1. I want a push notification on my phone. (Never done)
  2. Use Apple WeatherKit. (Never used)
  3. Make a server to check the weather and send a push notification to…
  4. Oh yes. Need an iOS app too.
  5. Writing a server? Why not use Go? (Minimal Go experience)
  6. Okay.
  7. What? Why?

All of this made sense in the moment. Using WeatherKit seems like a cool idea. I’ve wanted to give that a try and it has a REST API. Go for the server?? I’ve hardly used Go! I should’ve used Vapor. Better yet, scrap all this and use IFTTT or something. But I was already too far gone. My brain was obsessed and I stayed up to about 2am working on it. It was kinda fun.

The result is a small Go server that should hopefully check the weather at 7pm and send me a push notification if the temperature looks like it will be close to freezing.

The iOS app is literally the SwiftUI starter that requests notification permission when it launches. I archived it and picked ad-hoc distribution and installed it though the device manager in Xcode. Never done that before either. That’s kinda cool.

One little tidbit was how to get the correct string representation of the device token. It’s given to you as Data but it needs to be the hex values. I tried .base64EncodedString() but that did not work. Some googling found this:

deviceToken.map { String(format: "%02.2hhx", $0) }.joined()

It works.

Anyway. I’m tired. I worked on this too much, and my head hurts. But it was fun. Want a couple more details?

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