1

I have a simple struct that I will build upon. Right now it has one field, an Int.

struct Card: CustomStringConvertible  {
  let value: Int

  init(value: Int) {
    self.value = value
  }

  var description: String {
    return "\(String(value))"
  }
}

If I do this, I get the Card to print it's value

let c = Card(value: 1)
print(c)

Now if I put an array of Cards in a CardController like this:

class CardController: ObservableObject {
  @Published
  var cards: [Card] = [
    Card(value: 1),
    Card(value: 2),
    Card(value: 3)
  ]

Picker(selection: $selectedCardValue, label: Text("Choose a card")) {
        ForEach(0..<cardController.cards.count) {
          Text(self.cardController.cards[$0])
        }
      }
      Text("You selected \(selectedCardValue)")

I'll get the error Initializer 'init(_:)' requires that 'Card' conform to StringProtocol. I'm not sure why I get this error. If I instead just change the cards to a type of [String] and values ["1", "2", "3"], the code works fine.

Any idea what's wrong here?

2 Answers 2

8

As E.Coms noted, the solution is to use one of the following:

Text(self.cardController.cards[$0].description)

Text(String(describing: self.cardController.cards[$0]))

Here's an explanation of why you have to do this inside the Text initializer, but not print().


Look at the two initializers for Text:

init(verbatim content: String) (docs)

init<S>(_ content: S) where S : StringProtocol (docs)

You must pass either a String or a Substring, the only two types conforming to StringProtocol. In this case, even though your type conforms to CustomStringConvertible, you are still passing a Card.


Contrast this with something like the print function:

func print(_ items: Any..., separator: String = " ", terminator: String = "\n") (docs)

Notice that the print function's arguments are denoted by Any, which is explained as

Any can represent an instance of any type at all, including function types.

The print function then converts whatever you passed it to a String:

The textual representation for each item is the same as that obtained by calling String(item).

String has an initializer which takes a type conforming to CustomStringConvertible and returns the description property.


So the reason you can write print(Card()) and not Text(Card() is because the print function has an intermediate step through String that can understand your conformance to CustomStringConvertible, but Text does not. If Text allowed you to pass it any type, it would be both more ambiguous ("What is the text representation of this type?" is not necessarily immediately apparent, as it depends on a hierarchical set of protocols), and more work for the SwiftUI system, which is already doing a lot.

Sign up to request clarification or add additional context in comments.

2 Comments

I really appreciate the thorough explanation. Thank you.
for some reason .description did not work for me, but String(describing:... did the trick. Thanks!
0

You may miss the description by chance.

 ForEach(0..<cardController.cards.count) {
    Text(self.cardController.cards[$0].description)
 }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.