Reputation: 1358
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 Text
s don't work but the last one is OK? Thanks.
Upvotes: 2
Views: 1917
Reputation: 54486
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
.
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
}
...
}
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
Reputation: 30361
Instead of:
let text1 = Text("\(a)")
Do:
let text1 = Text(a.description)
Do the same for the other one.
Upvotes: 0
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