Statically Extractable Strings and DRY

DisplayRepresentation wants me to give it an image created using a string literal for systemName.

I can see why it needs this.

However, elsewhere in my code I have a bunch of static constants defined (in an extension on String). I use these so that I can specify the icon for any given concept in a single place, even if it is used in multiple user interface elements.

For example, I can say systemName: .navigateToItemsIcon instead of systemName: "note.text".

It irks me that I can’t use my constants in my display representation definitions.

In an ideal world, the compiler should be able to work out that String.navigateToItemsIcon resolves to a known value at compile time, and treat it the same way as it would treat a string literal.

Apparently, it doesn’t.

Is there any way to square this circle?

If your strings are constant anyways declares them as StaticString. As an alternative you could instead extend the type that use the constant like Image.systemBack instead of Image(systemName: .systemBack)

You can even combine the two methods extracting strings to constants and then using those constants to build common usage.

1 Like

Hm… works for me in Swift Playground:

import SwiftUI

extension String {
    static let iconNote = "note.text"
    static let iconCheckmark = "checkmark.message.fill"
}

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: .iconCheckmark)
                .resizable()
                .scaledToFit()
            Image(systemName: .iconNote)
                .resizable()
                .scaledToFit()
        }
    }
}

Yes, the example you’ve given works fine. That wasn’t what I was asking about, however.

The App Intents framework does some clever build-time extraction which relies on certain parameters being literal values.

Can you give more example code?

This thread gives a little more context:

Actually there may be a clue in that thread, in the form of proposal SE-0359.

If I turn that on, I might be able to tag my constants with @const. :thinking:

It looks like there is a CompileTimeValues experimental feature flag to enable @const. I’ll see what happens when I turn that on!

1 Like

Looks like that feature is currently implemented as _const, but sadly it doesn’t appear to help for my use case.

Could you redefine your constants as extensions on DisplayRepresentation.Image (or some other type that offers similar ergonomics)?

What do you mean by can't here? Does it actually not work (ignoring the warning)?

I could see that you can't use anything but a string literal for the title parameter though (as it ends with a compilation error if you try to use anything else).

There's some serious voodoo magic going on here.

import AppIntents

extension DisplayRepresentation {
    // same as built-in one, just with renamed first parameter:
    init(title2: LocalizedStringResource, subtitle: LocalizedStringResource? = nil, image: DisplayRepresentation.Image? = nil) {
        fatalError("TODO")
    }
}

let caseDisplayRepresentations: [String: DisplayRepresentation] = [
    "items": .init(title: ""), // ✅
    "tags": .init(title2: ""), // ✅
]

enum NavigateDestination: String, AppEnum {
    case items
    case tags
    
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Hello"
    
    static let caseDisplayRepresentations: [NavigateDestination: DisplayRepresentation] = [
        .items: .init(title: ""),  // ✅
        .tags: .init(title2: ""),  // ❌ Error: Expected valid title for case 'tags', got nil instead.
    ]
}
2 Likes