Issues with custom string interpolation in SwiftUI Text Views

I created a custom string interpolation function that allows me to pass in a string format specifier. I've noticed that, for whatever reason it does not work inside of a SwiftUI Text view unless I use the init(verbatim:) initializer. Consider this code:

import SwiftUI

struct ContentView: View {
    
    let price = 5.99
    var body: some View {
        Text("The price is $\(price, format: "%.2f")")
    }
}

extension String.StringInterpolation {

    mutating func appendInterpolation(_ value: Double, format: String) {
        appendInterpolation(String(format: format, value))
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I get the error
Incorrect argument label in call (have '_:format:', expected '_:specifier:')
It seems that there's some other interpolation function using '_:specifier:', but I want to use my own custom function.

If I change
Text("The price is $\(price, format: "%.2f")")
to
Text(verbatim: "The price is $\(price, format: "%.2f")")
then it works fine. Why?

How can I implement the interpolation function in such a way that it works inside Text views?

Text.init uses LocalizedStringKey, so you need to extend the interpolation there.

Also, I’d try to avoid using a common structure like \(double, format: format) in case SwiftUI decides to add it unless it’s intentional, maybe add some prefix?

@Lantua Is there anyway to extend both String and LocalizedStringKey using a protocol extension? I'd prefer not to duplicate code.

1 Like

If this is all you want, LocalizedStringKey can do it already, just do:

Text("The price is $\(price, specifier: "%.2f")")

@young But what if I want to make custom interpolation functions that work with both LocalizedStringKey and normal strings? How do I avoid writing a separate interpolation function for each?

The Text("abcd string literal") initializor is meant for localization as it do lookup of Localizable.strings. You might be able to extend it, but it worn't do lookup of your extension, I don't think.

If you just want your own interpolation on String, use:

Text(verbatim: "The price is $(price, format: "%.2f")")

This one takes a String so your custom interpolation should just work, but then you cannot localize it.

I don't care about lookups, but I also don't want to have to type the extra parameter verbatim every time I want to do string interpolations. I've discovered how to write a extension for localized strings and for regular strings, but I would like to know if I can write just a single function in a protocol extension so that I don't have to duplicate code. Is this possible? Currently, I have to do this. Notice that the functions are exactly the same.

public extension LocalizedStringKey.StringInterpolation {
    mutating func appendInterpolation(_ value: Double, format: String) {
        appendInterpolation(String(format: format, value))
    }
}

public extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: Double, format: String) {
        appendInterpolation(String(format: format, value))
    }
}

In this case, then it makes no sense to extend LocalizedStringKey.StringInterpolation

Text(_:)
Text(verbatim:)

are two different functions, you have to type it out so the compiler can tell what you want. It can't read you mind.

Making a new protocol with the correct required method and extension, and then extending LocalizedStringKeys/String.StringInterpolation with this new protocol seems to work for me:

protocol Formattable {
    mutating func appendInterpolation(_: String)
}

extension Formattable {
    mutating func appendInterpolation(_ value: Double, format: String) {
        appendInterpolation(String(format: format, value))
    }
}

extension LocalizedStringKey.StringInterpolation: Formattable {}
extension String.StringInterpolation: Formattable {}
2 Likes

I should've been more clear about this. What I meant was that I don't want to have to type Text(verbatim: "abc") everytime I want to do a string interpolation. I want to be able to just type Text("abc") and be able to use the same string interpolation functions as if I had just used a normal string.

@odmir has provided the exact answer that I'm looking for. Thanks Odmir!

Don't forget about how powerful protocol extensions are.

1 Like