Is there a way to make a copy of what i want to append into an array

I am trying to append multiple objects [of "recipe" type] into an array and then be able to modify them independently afterward in an edit view here is the code that doesn't work:

VStack{
     Button("add") {

     Recipe.steps.append(recipe.step.default)
   }

 ForEach($Recipe.steps){step in

                    TextEditor(text: step.text)

                    Text(" ")

                }
}

what happens is when i append the default step in the array multiple times and then modify on of them they all sync up

I could not find answers online

Thanks

sam

Answered by DTS Engineer in 742628022

When you create a list of things in SwiftUI (using ForEach, for example) the items in the list need to have a unique identity, so that SwiftUI can tell them apart. The standard way of doing this is to make the type (a recipe step in this case) conform to protocol Identifiable, and then make sure that different steps have different (but persistent) id values.

It sounds like your recipe steps all have the same identity, so everything in your list displays the same value, and changing one changes them all.

I think it'd be helpful for you to spend some more time learning about how SwiftUI uses data in lists. There are plenty of 3rd party tutorials out there, as well as in Apple's documentation: https://developer.apple.com/tutorials/swiftui.

Accepted Answer

When you create a list of things in SwiftUI (using ForEach, for example) the items in the list need to have a unique identity, so that SwiftUI can tell them apart. The standard way of doing this is to make the type (a recipe step in this case) conform to protocol Identifiable, and then make sure that different steps have different (but persistent) id values.

It sounds like your recipe steps all have the same identity, so everything in your list displays the same value, and changing one changes them all.

I think it'd be helpful for you to spend some more time learning about how SwiftUI uses data in lists. There are plenty of 3rd party tutorials out there, as well as in Apple's documentation: https://developer.apple.com/tutorials/swiftui.

With the very partial code you show, it is hard to be sure what are all the errors.

  • What is Recipe ? Why starting with uppercase
  • What is recipe ?
  • What is default (it is a keyword)
  • What are Recipe.steps

What is recipe.step ?

As Polyphonic stated, a first one is that objects in List have not a unique identifier, hence the List management gets confused.

You may receive an error in log, such as:

ForEach<LazyMapSequence<Range, (Int, String)>, String, TupleView<(TextEditor, Text)>>: the ID default occurs multiple times within the collection, this will give undefined results!

Your ForEach should look more like this

           ForEach($recipe.steps, id: \.self) { $step in
                TextEditor(text: $step)
                Text(" ")
            }

But please show whole code as there are parts which I don't understand, such as:

     Recipe.steps.append(recipe.step.default)

here is the declaration for recipe:

struct recipe: Codable, Identifiable, Hashable {

    private let imageName : String

    struct step: Codable, Identifiable, Hashable {

        var id: Int

        var isDone: Bool

        var text : String

        

        static let `default` = step(id: 00, isDone: false, text: "default step")

    }

    struct ingredient: Hashable, Identifiable, Codable {

        var id: Int

        var hasIt: Bool

        let quantity : Double

        let mesurment : String

        let name : String

        

        static let `default` = ingredient(id: 00, hasIt: false, quantity: 1, mesurment: "cup", name: "default")

    }

    

    var id: Int

    

    

    var Name : String

    var image: Image{

        Image(imageName)

    }

    var description : String

    var steps : [step]

    var ingredients : [ingredient]

    var notes: String

    var link: String

    

    static let `default` = recipe(imageName: "image", id: 00, Name: "default", description: "enter des.", steps: [step(id: 00, isDone: false, text: "step1"), step(id: 01, isDone: false, text: "step 2")], ingredients: [ingredient(id: 00, hasIt: false, quantity: 10.0, mesurment: "cup", name: "ing. 1")], notes: "notes", link: "google.com")

}

then i define my array of recipes from a json file:


    @Published var recipes: [recipe] = load("data.json")

}

func load<T: Decodable>(_ filename: String) -> T {

    let data: Data



    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)

    else {

        fatalError("Couldn't find \(filename) in main bundle.")

    }



    do {

        data = try Data(contentsOf: file)

    } catch {

        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")

    }



    do {

        let decoder = JSONDecoder()

        return try decoder.decode(T.self, from: data)

    } catch {

        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")

    }

}

after that i have some other files that pass down the recipe to the file:


    @Binding var Recipe: recipe

    



    

    var body: some View {

        List {

            HStack {

                Text("name")

                Divider()

                TextField("name", text: $Recipe.Name)

            }

            ZStack(alignment: .leading) {

                Text("description ")

                    .font(.system(.body))

                    .foregroundColor(.clear)

                    .padding(14)

                

                

                TextEditor(text: $Recipe.description)

                    .font(.system(.body))

                    .frame(height: max(60, 200))

                    .cornerRadius(10.0)

                    .shadow(radius: 1.0)

            }

            VStack{

                Button("add") {

                    Recipe.steps.append(recipe.step.default.self)

                    //test()

                }

                

                /*ForEach($Recipe.steps, id: \.self) { $Step in

                    TextField(String(Step.id), text: $Step.text)

                    Text(" ")

                }*/

                ForEach($Recipe.steps){$step in

                    

                    TextField(String($step.id), text: $step.text)

                    

                    Text(" ")

                    

                }

            }

        }

    }

}



struct editView_Previews: PreviewProvider {

    static var previews: some View {

        editView(Recipe: .constant(recipe.default))

    }

}

I'm making this app parallel to the Landmarks app tutorial and the structure is very similar.

as you can see i tested with the id declaration but it has a wired behaviour to unselect the text in the textfield after I edit a character any idea why adding the id thing would do that?

anyway it fixed the problem

thanks for the fat answers sam

btw is there a way to upload a project or do I need to pass by a git hub like site?

sorry for my very limited knowledge; I am still learning swiftUI

Is there a way to make a copy of what i want to append into an array
 
 
Q