This article is reprinted from: onevcat.com/2021/03/swi… For the purpose of conveying more information, the copyright of this article belongs to the original author or source organization.

Interpolation in Text

Text is the simplest and most common View in SwiftUI. For basic use, we can create a Text by passing in a string literal:

Text("Hello World")
Copy the code

In iOS 14 (SwiftUI 2.0), Apple has made a number of enhancements to Text interpolation. In addition to simple Text, we can insert an Image directly into a Text:

Text("Hello (Image(systemName: "globe"))")
Copy the code

This is a very powerful feature that greatly simplifies text mixing code. In addition to plain strings and images, string interpolation in Text can accept a number of other “weird” types, some of which even accept formatter for the incoming property, which brings us a lot of convenience:

Text("Date: (Date(), style: .date)")
Text("Time: (Date(), style: .time)")
Text("Meeting: (DateInterval(start: Date(), end: Date(timeIntervalSinceNow: 3600)))")

let fomatter: NumberFormatter = {
    let f = NumberFormatter()
    f.numberStyle = .currency
    return f
}()
Text("Pay: (123 as NSNumber, formatter: fomatter)")
Copy the code

At the same time, however, some of the most common string interpolation methods are not supported in Text. Typically, we can experience two errors with appendInterpolation:

Text("3 == 3 is (true)")  // No exact matches in call to instance method 'appendInterpolation' struct Person { let name: String let place: String } Text("Hi, (Person(name: "Geralt", place: // Instance method 'appendInterpolation' requires that 'Person' conform to '_FormatSpecifiable'Copy the code

AppendInterpolation = _FormatSpecifiable = _FormatSpecifiable = appendInterpolation = _FormatSpecifiable How do you make these types work with Text?

I intend to spend two articles exploring this topic and related API design.

In this article, we’ll start by looking at why Text can accept Image or Date as interpolation, but not Bool or Person, referring to the custom string interpolation feature introduced in Swift 5. Later in the next installment, we’ll explore the deeper aspects behind this topic and see how localization in SwiftUI actually works. We will also discuss how to solve the corresponding problems and use these features to write more correct and beautiful SwiftUI code.

Hero behind the scene: LocalizedStringKey

SwiftUI put multilingual localization support in the first place, in the direct use to initialize a string literal Text, the call to the method in fact is the init (_ : tableName: bundle: the comment:) :

extension Text {
    init(
        _ key: LocalizedStringKey, 
        tableName: String? = nil, 
        bundle: Bundle? = nil, 
        comment: StaticString? = nil
    )
}
Copy the code

Text uses the input key to find the localized string file in the bundle and renders the result for the device language.

Because LocalizedStringKey meet ExpressibleByStringInterpolation ExpressibleByStringLiteral (and his father agreement), it can directly convert by string literals. In other words, in the example above, whether the interpolated Image or Date is the Image or Date, the result, as input to the Text initialization method, is actually a LocalizedStringKey instance.

For string literals, Text uses the LocalizedStringKey overload above. If the String is stored in a String first, such as let s = “hello”, then Text(s) will pick another StringProtocol initialization method: init< s >(_ content: s) where s: StringProtocol.

Another important initialization method for Text is init(verbatim:). If you don’t need localization at all, using this method will let you use the input string directly, skipping LocalizedStringKey entirely.

We can prove this by simply trying to print the above interpolated string as normal string interpolation would:

print("Hello (Image(systemName: "globe"))") // Hello Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $1b472d684).NamedImageProvider>) print("Date: (Date(), style:.date)") // Cannot infer contextual base in reference to member 'Date'Copy the code

Image interpolation directly uses the standard description of struct and returns a normal string; The interpolation of Date does not accept additional parameters, giving a compilation error. Either way, you can’t pass it to Text as a simple string and get the final rendering.

In fact, in the Text initialization method, this kind of interpolation uses the LocalizedStringKey correlation interpolation method. This is also a new feature in Swift 5 that allows us to interpolate any type of input (such as Image) and even set some parameters (such as Date and its.date style parameter) while interpolating.

StringInterpolation

Plain string interpolation is a feature that Swift has had since its inception. We can use (variable) to add a value to a String literal that can be represented as a String:

print("3 == 3 is (true)")
// 3 == 3 is true

let luckyNumber = 7
print("My lucky number is (luckNumber).")
// My lucky number is 7.

let name = "onevcat"
print("I am (name).")
// I am onevcat.
Copy the code

In Swift 5, literal interpolation is enhanced. We can keep a type to ExpressibleByStringInterpolation customize interpolation behavior. This feature has been discussed quite a bit, but let’s take a look at its basic usage again to get you familiar and recall it more quickly.

Swift String in the standard library is to meet the agreement, to extend the String supported by the type of interpolation, we can expand the String. StringInterpolation types of implementation, it needed to add the appropriate type. Use the Person shown above as an example. Without modification, print will print the Person value in the Swift struct default format:

struct Person {
    let name: String
    let place: String
}

print("Hi, (Person(name: "Geralt", place: "Rivia"))")
// Hi, Person(name: "Geralt", place: "Rivia")
Copy the code

If we want a name more role play, can consider to extend the String. StringInterpolation, add a appendInterpolation (_ person: Person) method, which defines the behavior of a string literal when it receives a Person:

extension String.StringInterpolation { mutating func appendInterpolation(_ person: Person) {// call 'appendLiteral(_ literal: String)' accepts' String 'arguments appendLiteral("(person.name) of (person.place)")}}Copy the code

Now, the Person interpolation in String will change:

print("Hi, (Person(name: "Geralt", place: "Rivia"))")
// Hi, Geralt of Rivia
Copy the code

In the case of multiple parameters, we can in the String. StringInterpolation add new parameters, and the interpolation with similar “method call” writing, the parameter passed in:

Struct Person {let name: String let place: String var nickName: String? } extension Person {var formalTitle: String {"(name) of (place)"} Bool) -> String { isFriend ? (nickName ?? formalTitle) : formalTitle } } extension String.StringInterpolation { mutating func appendInterpolation(_ person: Person, isFriend: Bool) { appendLiteral(person.title(isFriend: isFriend)) } }Copy the code

Call with isFriend:

let person = Person(
    name: "Geralt", place: "Rivia", nickName: "White Wolf"
)
print("Hi, (person, isFriend: true)")
// Hi, White Wolf
Copy the code

String interpolation for LocalizedStringKey

Image and Date

Now that we know the StringInterpolation, we can see how LocalizedStringKey handles interpolation in Text context. Similar to ordinary String, LocalizedStringKey also abide by the ExpressibleByStringInterpolation, And SwiftUI already provides some common extensions for its StringInterpolation. In the current SwiftUI implementation (iOS 14), they include:

extension LocalizedStringKey.StringInterpolation {
    mutating func appendInterpolation(_ string: String)
    mutating func appendInterpolation<Subject>(_ subject: Subject, formatter: Formatter? = nil) where Subject : ReferenceConvertible
    mutating func appendInterpolation<Subject>(_ subject: Subject, formatter: Formatter? = nil) where Subject : NSObject
    mutating func appendInterpolation<T>(_ value: T) where T : _FormatSpecifiable
    mutating func appendInterpolation<T>(_ value: T, specifier: String) where T : _FormatSpecifiable
    mutating func appendInterpolation(_ text: Text)
    mutating func appendInterpolation(_ image: Image)
    mutating func appendInterpolation(_ date: Date, style: Text.DateStyle)
    mutating func appendInterpolation(_ dates: ClosedRange<Date>)
    mutating func appendInterpolation(_ interval: DateInterval)
}
Copy the code

In the example in the first part of this article, the interpolation of Image and Date style is done using exactly the method declared above. After receiving the correct parameter type, the final LocalizedStringKey is obtained by creating the appropriate Text. We can easily write two appendInterpolation implementations in our example:

mutating func appendInterpolation(_ image: Image) {
    appendInterpolation(Text(image))
}

mutating func appendInterpolation(_ date: Date, style: Text.DateStyle) {
    appendInterpolation(Text(date, style: style))
}
Copy the code

Bool and Person

Now, it’s easy to see why Bool and Person can’t be used directly in Text in the top example.

The Bool:

Text("3 == 3 is (true)") // No exact matches in call to instance method 'appendInterpolation'Copy the code

LocalizedStringKey does not extend the appendInterpolation method to Bool, so there is no way to generate an instance of LocalizedStringKey using interpolation.

For Person, the initial error is relatively hard to understand:

Text("Hi, (Person(name: "Geralt", place: // Instance method 'appendInterpolation' requires that 'Person' conform to '_FormatSpecifiable'Copy the code

With the existing appendInterpolation implementation in SwiftUI, you can see that it actually uses:

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

This is the closest overloaded method, but since Person does not implement the _FormatSpecifiable private protocol, there is essentially no interpolation method. To fix this, we can either add appendInterpolation to the Person or have it comply with the _FormatSpecifiable private protocol. However, the two methods are fundamentally different, and can sometimes have unexpected results depending on the actual use scenario. We’ll cover this topic in more detail in the next article in this series.

summary

  • SwiftUI 2.0 can be used toTextThe interpolationImage 和 DateSuch nonStringValue, which makes it very easy to mix text or format text.
  • Flexible interpolation benefits from the introduction of Swift 5.0ExpressibleByStringInterpolation. You can have theStringCustom interpolation, and even for any type of custom string interpolation.
  • Class with a string literalTextIs the type of the argumentLocalizedStringKey.
  • LocalizedStringKeyAcceptance is achievedImageorDateInterpolation method, so we can create inTextWhen directly insertedImageOr formattedDate.
  • LocalizedStringKeyDon’t acceptBoolOr interpolation parameters of a custom type. We could add related methods, but that would have side effects.
  • Download all kinds of technical documents of iOS