lochiwei
lochiwei

Reputation: 1358

Argument type 'xxx' does not conform to expected type '_FormatSpecifiable'

While I was playing around with SwiftUI, I ran into two unexpected errors:

import SwiftUI

struct MyStruct: CustomStringConvertible {
    var description: String { "hello" }
}

class MyClass: CustomStringConvertible {
    var description: String { "hi" }
}

let a = MyStruct()
let b = MyClass()
let s1 = "\(a)"        // hello
let s2 = "\(b)"        // hi

// ❌ Argument type 'MyStruct' does not conform to
//    expected type '_FormatSpecifiable'
let text1 = Text("\(a)")

// ❌ Cannot convert value of type 'MyClass' to
//    expected argument type 'String'
let text2 = Text("\(b)")

let n = 1

// ✅ this one is OK, why?
let text3 = Text("\(n)")

Does anyone know what these errors mean? And why the first two Texts don't work but the last one is OK? Thanks.

Upvotes: 2

Views: 1917

Answers (3)

pawello2222
pawello2222

Reputation: 54486

Problem

Conforming to CustomStringConvertible allows you to customise the description which can later be accessed with String(describing:):

let a = MyStruct()
let text = String(describing: a)

However, by calling:

let text1 = Text("\(a)")

you're effectively calling this constructor:

public init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)

Note that "\(a)" is not a String - it's a LocalizedStringKey.

Error

Argument type 'MyStruct' does not conform to expected type '_FormatSpecifiable'

LocalizedStringKey already conforms to ExpressibleByStringInterpolation but its inner struct (StringInterpolation) doesn't have methods that accept parameters of type MyStruct.

See

public mutating func appendInterpolation<T>(_ value: T) where T : SwiftUI._FormatSpecifiable

in the implementation of LocalizedStringKey:

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct LocalizedStringKey : Swift.Equatable, Swift.ExpressibleByStringInterpolation {
  ...
  public struct StringInterpolation : Swift.StringInterpolationProtocol {
    public init(literalCapacity: Swift.Int, interpolationCount: Swift.Int)
    public mutating func appendLiteral(_ literal: Swift.String)
    public mutating func appendInterpolation(_ string: Swift.String)
    public mutating func appendInterpolation<Subject>(_ subject: Subject, formatter: Foundation.Formatter? = nil) where Subject : Foundation.ReferenceConvertible
    public mutating func appendInterpolation<Subject>(_ subject: Subject, formatter: Foundation.Formatter? = nil) where Subject : ObjectiveC.NSObject
    public mutating func appendInterpolation<T>(_ value: T) where T : SwiftUI._FormatSpecifiable
    public mutating func appendInterpolation<T>(_ value: T, specifier: Swift.String) where T : SwiftUI._FormatSpecifiable
    @available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
    public mutating func appendInterpolation(_ text: SwiftUI.Text)
    public typealias StringLiteralType = Swift.String
  }
  ...
}

Solution

You need to manually create an extension to accept MyStruct parameters:

extension LocalizedStringKey.StringInterpolation {
    mutating func appendInterpolation(_ value: MyStruct) {
        appendInterpolation(String(describing: value))
    }
}

Now you can just call:

let text1 = Text("\(a)")

Alternatively you can use a different init to make sure you're passing a String and not a LocalizedStringKey:

let text1 = Text(verbatim: "\(a)")

Useful links:

Upvotes: 3

George
George

Reputation: 30361

Instead of:

let text1 = Text("\(a)")

Do:

let text1 = Text(a.description)

Do the same for the other one.

Upvotes: 0

Asperi
Asperi

Reputation: 257711

This means that Text does not have constructor matching those cases, use instead

let text1 = Text(String("\(a)"))
let text2 = Text(String("\(b)"))

Tested with Xcode 12.1 / iOS 14.1

Upvotes: 0

Related Questions