In the previous article, we saw why Text, or more accurately LocalizedStringKey, can accept Image and Date, but not Bool or custom Person types. In this next article, let’s look at specific ways to make Text support other types.

Custom interpolation for LocalizedStringKey

If we just want Text to accept true or false directly, we can simply overload Bool with appendInterpolation.

extension LocalizedStringKey.StringInterpolation {
    mutating func appendInterpolation(_ value: Bool) {
        appendLiteral(value.description)
    }
}
Copy the code

This way, we can avoid compilation errors:

Text("3 == 3 is (true)")
Copy the code

For Person, we can also add appendInterpolation to add the Person version of the interpolation method directly to LocalizedStringKey:

extension LocalizedStringKey.StringInterpolation {
    mutating func appendInterpolation(_ person: Person, isFriend: Bool) {
        appendLiteral(person.title(isFriend: isFriend))
    }
}
Copy the code

The above code for LocalizedStringKey. StringInterpolation added Bool and Person’s support, but it actually destroys the localization support. This may not have the desired effect and may even lead to unexpected behavior. Use with caution until fully understood. We’ll talk more about this topic later in this article in the section on localization.

LocalizedStringKey

Find localized values by key

We’ve spent a lot of time just messing around with LocalizedStringKey and its interpolation. In retrospect, it seems that we haven’t paid any attention to what LocalizedStringKey itself is. As its name suggests, LocalizedStringKey is the type SwiftUI uses to find the key in Localization. Strings. Try printing the simplest LocalizedStringKey:

let key1: LocalizedStringKey = "Hello World"
print(key1)
// LocalizedStringKey(
//     key: "Hello World", 
//     hasFormatting: false,
//     arguments: []
// )
Copy the code

It looks for the string corresponding to the “Hello World” key. For example, in a localized string file there is a definition like this:

// Localization. Strings "Hello World"=" Hello World";Copy the code

When used, SwiftUI will select the result based on the value of localizedStringKey. key:

Text("Hello World")
Text("Hello World")
    .environment(.locale, Locale(identifier: "zh-Hans"))
Copy the code

Interpolate the key of LocalizedStringKey

So now the interesting part is, what’s the key of this LocalizedStringKey?

let name = "onevcat"
let key2: LocalizedStringKey = "I am (name)"
Copy the code

Is it “I am onevcat”? If so, how will the string be localized? If not, what would the key be?

Print it out and see:

print(key2)

// LocalizedStringKey(
//     key: "I am %@", 
//     hasFormatting: true, 
//     arguments: [
//         SwiftUI.LocalizedStringKey.FormatArgument(
//             ...storage: Storage.value("onevcat", nil)
//         )
//     ]
// )
Copy the code

The key is not a fixed “I am onevcat”, but a String formatter: “I am %@”. For readers familiar with String Format, this will be familiar: Name is passed as a variable into String Format, replacing the %@ placeholder for the object. So, when localizing this string, the key we need to specify is “I am %@”. Of course, this LocalizedStringKey could also correspond to any other input:

// Localization. Strings "I am %@"=" I am %@"; Swift Text("I am ("onevcat")") // swift Text("I am ("onevcat")") // Swift Text("I am ("onevcat")") // Swift Text("I am ("onevcat")"Copy the code

For Image interpolation, the situation is similar: the interpolated portion of the Image is converted to %@ to meet the need for localized keys:

let key3: LocalizedStringKey = "Hello (Image(systemName: "globe"))" print(key3) // LocalizedStringKey( // key: "Hello %@", // ... / / / / Localization strings / / "Hello % @" = "Hello, % @"; Text("Hello (Image(systemName: "globe"))") Text("Hello (Image(systemName: "globe"))") .environment(.locale, Locale(identifier: "zh-Hans"))Copy the code

It is worth noting that the formatting symbol for the interpolation of Image is %@, which is the same symbol for the interpolation of String or any other object. That is, the same localized string is found in the following two interpolation methods:

Text("Hello ("onevcat")")
    .environment(.locale, Locale(identifier: "zh-Hans"))
Text("Hello (Image(systemName: "globe"))")
    .environment(.locale, Locale(identifier: "zh-Hans"))
Copy the code

Other types of interpolation formatting

As you might have guessed, LocalizedStringKey supports other types of formatting in addition to %@. For example, when interpolating ints, the key argument is converted to % LLD; For Double, convert to %lf and so on:

let key4: LocalizedStringKey = "Hello (1))" // LocalizedStringKey(key: "Hello %lld) let key5: LocalizedStringKey = "Hello (1.0))"Copy the code

Using Hello % LLD or Hello %lf will not match the previous Hello %@ in the localization file.

A more reasonable appendInterpolation implementation

Avoid appendLiteral

Now let’s get back to the interpolation of Bool and Person. At the beginning of this article, we added two interpolation methods to make LocalizedStringKey accept the interpolation of Bool and Person:

mutating func appendInterpolation(_ value: Bool) {
    appendLiteral(value.description)
}

mutating func appendInterpolation(_ person: Person, isFriend: Bool) {
    appendLiteral(person.title(isFriend: isFriend))
}
Copy the code

In both methods we use appendLiteral to add String directly to the key, so we get a full LocalizedStringKey with no arguments, which in most cases is not what we want:

let key6: LocalizedStringKey = "3 == 3 is (true)"
// LocalizedStringKey(key: "3 == 3 is true", ...)

let person = Person(name: "Geralt", place: "Rivia", nickName: "White Wolf")
let key7: LocalizedStringKey = "Hi, (person, isFriend: false)"
// LocalizedStringKey(key: "Hi, Geralt of Rivia", ...)
Copy the code

When implementing the new appendInterpolation, respect the insertion parameters and forward the actual insertion action to the existing appendInterpolation implementation, leaving the LocalizedStringKey type to handle key composition and formatting characters. It would have been more reasonable and generic:

mutating func appendInterpolation(_ value: Bool) {
    appendInterpolation(value.description)
}

mutating func appendInterpolation(_ person: Person, isFriend: Bool) {
    appendInterpolation(person.title(isFriend: isFriend))
}

let key6: LocalizedStringKey = "3 == 3 is (true)"
// LocalizedStringKey(key: "3 == 3 is %@", ...)

let key7: LocalizedStringKey = "Hi, (person, isFriend: false)"
// LocalizedStringKey(key: "Hi, %@", ...)
Copy the code

Add styles for Text

With interpolation using the LocalizedStringKey parameter and the existing appendInterpolation, you can write some handy methods. For example, we can add a set of string formatting methods to make Text styling easier:

extension LocalizedStringKey.StringInterpolation {
    mutating func appendInterpolation(bold value: LocalizedStringKey){
        appendInterpolation(Text(value).bold())
    }

    mutating func appendInterpolation(underline value: LocalizedStringKey){
        appendInterpolation(Text(value).underline())
    }

    mutating func appendInterpolation(italic value: LocalizedStringKey) {
        appendInterpolation(Text(value).italic())
    }
    
    mutating func appendInterpolation(_ value: LocalizedStringKey, color: Color?) {
        appendInterpolation(Text(value).foregroundColor(color))
    }
}
Copy the code
Text("A \(bold: "wonderful") serenity \(italic: "has taken") \("possession", color: .red) of my \(underline: "entire soul").")
Copy the code

The following results can be obtained:

The corresponding key is “A % @serenity % @% @of my %@.” . Interpolation is considered a placeholder for parameters. In some cases it might not be the result you want, but the localization of the view string is also an annoying thing in UIKit. Compared to UIKit, SwiftUI’s progress in this area is obvious.

about_FormatSpecifiable

Finally let’s look at the _FormatSpecifiable problem. You may have noticed, the LocalizedStringKey, built. There are two method involves _FormatSpecifiable StringInterpolation:

mutating func appendInterpolation<T>(_ value: T) where T : _FormatSpecifiable
mutating func appendInterpolation<T>(_ value: T, specifier: String) where T : _FormatSpecifiable
Copy the code

Specifies the placeholder format

Part of the basic type in Swift is the _FormatSpecifiable private protocol. This protocol helps LocalizedStringKey select an appropriate placeholder representation when concatenating keys, such as % LLD for Int, % LF for Double, and so on. When we use Int or Double for interpolation, the overloaded method above will be used:

Text("1.5 + 1.5 = (1.5 + 1.5)") LocalizedStringKey = "1.5 + 1.5 = (1.5 + 1.5)" print(key) // 1.5 + 1.5 = %lfCopy the code

The right side of the above Text equals sign will be rendered as %lf:

If you want to keep it to one decimal point, you can just use the version with the specifier parameter. When the key is generated, the specifier is passed in instead of the format that would have been used:

The Text (" 1.5 + 1.5 = (1.5 + 1.5, specifiers: "%. 1 lf") ") / / key: 1.5 + 1.5 = %. 1 lfCopy the code

Implementation for custom types_FormatSpecifiable

_FormatSpecifiable is relatively simple, though it is a private protocol:

protocol _FormatSpecifiable: Equatable {
    associatedtype _Arg
    var _arg: _Arg { get }
    var _specifier: String { get }
}
Copy the code

Let _arg return the actual value to be interpolated, and let _specifier return the placeholder format. Int: _FormatSpecifiable () ¶

extension Int: _FormatSpecifiable {
    var _arg: Int { self }
    var _specifier: String { "%lld" }
}
Copy the code

For the Person we use multiple times in our example, we can use a similar trick to satisfy _FormatSpecifiable:

extension Person: _FormatSpecifiable {
    var _arg: String { "(name) of (place)" }
    var _specifier: String { "%@" }
}
Copy the code

This way, even if we don’t add the Person interpolation method to LocalizedStringKey, the compiler will select the _FormatSpecifiable interpolation method for us, adding the Person description to the final key.

conclusion

On the basis of the previous chapter, in this paper:

  • We tried to extend itLocalizedStringKeyThe interpolation method allows it to be supportedBool 和 Person.
  • LocalizedStringKeyThe main task of interpolation is to automatically generate appropriate localized keys with parameters.
  • In an extensionLocalizedStringKeyInterpolation should be used whenever possibleappendInterpolationTo avoid parameter swallowing.
  • The interpolation format is made up of_FormatSpecifiableSure. We can also interpolate by having custom types implement this protocol.
  • Download all kinds of technical documents of iOS

At this point, we should have an idea of why an Image can be interpolated in a Text, and everything that happens behind it.