Where is the String interpolation from "Structure your app for SwiftUI previews" documented?

(I tried to post in the Apple dev forums but they don't really work yet.)

I'd never seen this before:

How are you supposed to format it if you define the arguments locally?

Text(
  """
  \(
    { var nameComponents = PersonNameComponents()
      nameComponents.givenName = "Given"
      nameComponents.familyName = "Family"
      return nameComponents as NSPersonNameComponents
    } (),
    formatter: {
      let formatter = PersonNameComponentsFormatter()
      formatter.style = .long
      return formatter
    } ()
  )
  """
)

You mean ExpressibleByStringInterpolation? Most of the SwiftUI types use LocalizedStringKey which has its own StringInterpolation. You can consult StringInterpolationProtocol on how the transformation work.

TD;DR, LocalizedStringKey supports:

  • \(x as String)
  • \(x as T, specifier: y as String) where T is the curious _FormatSpecifiable :woman_shrugging:
  • \(x as Subject, formatter: y as Formatter?) where
    • Subject is NSObject, or
    • Subject is ReferenceConvertible.

And the new ones (beta):

  • \(x as Text)
  • \(x as Image)
  • \(x as ClosedRange<Date>)
  • \(x as DateInterval)
  • \(x as Date, style: y as Text.DateStyle)

You can also add your own if you'd like.

3 Likes

Thanks!

Can it be formatted without """ or a giant line with semicolons?

I'm pretty sure it works the same way with all string literals (Strings and Characters–String Literal). They only differ in how special characters, like \r\n\t, are handled*.

I don't think it'd work with semicolon, a; b would make them multi-statements, rather than an expression, which is what the interpolation needs (they're just normal functions).

* Since #"extended string#" also changes how \ is handled. You'd need to match the number of # in the beginning for \ to have effect, so you'd need \###(x) instead of regular \(x) if you use ###"..."###.

1 Like

It works. I don't know the reasoning. I just saw \() not working on one line so I added more "s (the above) and that works too. They just seem redundant because it's not a multi-line literal.

Text(
  "\( { var nameComponents = PersonNameComponents(); nameComponents.givenName = "Given"; nameComponents.familyName = "Family"; return nameComponents as NSPersonNameComponents } (), formatter: { let formatter = PersonNameComponentsFormatter(); formatter.style = .long; return formatter } () )"
)

Oh I see. That would work. What you’re essentially doing is creating a closure (bag of statements) and then call it, not too dissimilar to calling a function. So it’s an expression alright.

Because this:

{
  var nameComponents = PersonNameComponents()
  nameComponents.givenName = "Given"
  nameComponents.familyName = "Family"
  return nameComponents as NSPersonNameComponents
} ()

is just this:

// Defined somewhere else
func foo() -> NSPersonNameComponent {
  var nameComponents = PersonNameComponents()
  nameComponents.givenName = "Given"
  nameComponents.familyName = "Family"
  return nameComponents as NSPersonNameComponents
}

// simple function call
foo()

And since Swift can also use semicolon to separate statements (in addition to new line), this:

let formatter = PersonNameComponentsFormatter()
formatter.style = .long
return formatter

is the same as this

let formatter = PersonNameComponentsFormatter(); formatter.style = .long; return formatter

The specifier argument takes a printf-style format string. Example:

Text("\(Double.pi, specifier: "%.4f")")

produces the text "3.1416", and

Text("0x\(255, specifier: "%llX")")

produces "0xFF".

SwiftUI uses the _FormatSpecifiable protocol to make this interpolation available to the standard number types. The integer and floating-point types from the stdlib conform to it, plus CGFloat. (Float16 doesn't in Xcode 12 beta 1 – I don't know if that's an oversight.)