swiftswiftuibindingswiftui-listswiftui-navigationlink

Using a single member of an array in both a bound and unbound way in ForEach in SwiftUI without using indices


Apologies because I'm new to SwiftUI -

I have a class (Meal) that contains an array of objects of another class (Food). What I'm trying to achieve here is to be able to add foods and have that update in a list, and then to be able to edit an individual food by tapping on it.

I'm iterating through the list of foods and using each food to (1) create a NavigationLink and (2) simply list the food's name in the list. The problem is that I get the error Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol' when I try to reference the food in an "unbound" way. Specifically, in the code below, it's at the line Text(food.name).

            ForEach($meal.foods) { food in
                NavigationLink(destination: FoodView(food: food)) {
                    Text(food.name)
                }
            }

I'm aware I could iterate through a list of indices instead (I think) but I think that's probably bad practice. I'm trying to figure out what the best practice is in this scenario. Thanks in advance!

Full code below:

// MARK: Views
struct ContentView: View {
    @State private var day = Day()
    
    var body: some View {
        MealView(meal: $day.meals.first!)
    }
}

struct MealView: View {
    @Binding var meal: Meal
    
    var body: some View {
        NavigationStack {
            List {
                ForEach($meal.foods) { food in
                    NavigationLink(destination: FoodView(food: food)) {
                        Text(food.name)
                    }
                }
            }
        }
 
    }
}

struct FoodView: View {
    @Binding var food: Food
    
    var body: some View {
        VStack {
            TextField("Food", text: $food.name)
        }
    }
}

// MARK: Data
class Day {
    var meals: [Meal] = [Meal()]
}

@Observable
class Meal: Identifiable {
    var id: UUID = UUID()
    var foods: [Food] = [Food(name: "Pizza"), Food(name: "Pasta"), Food(name: "Steak")]
}

@Observable
class Food: Identifiable {
    var id: UUID = UUID()
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

Solution

  • The error says

    Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'

    What this is saying is that the Text initialiser wants something that conforms to StringProtocol and Binding<String> doesn't; food is a Binding<String>. You need to access the string that is bound, which you can do with the binding's wrappedValue property -

    ForEach($meal.foods) { food in
        NavigationLink(destination: FoodView(food: food)) {
            Text(food.name.wrappedValue)
        }
    }