Danny
Danny

Reputation: 139

How to pass an array from a view to another and update the view accordingly

I have View1 that has a list, a Navigation button to View2, and an array with based on the items in the array, it will make rows.

In View2 I have a list of items.

I want the user to go to View2 and choose items then add them to the array that exist is View1. Then View1 gets updated with the new added items.

It problem is about passing items from one view to another. Also it would be great if View2 goes back to View1 when the user clicks an item.

This is what I tried to create which does not work:

View 1

struct ContentView: View {

    @State private var newItems : Array = []

    var body: some View {
        NavigationView {
            List(){
                Section(header:
                    HStack{
                        Image(systemName: "plus.square.fill")
                        Text("Find Items")
                    })
                    {
                        NavigationLink( destination: MyListView(newItems: self.$newItems) ){
                                Text("Add Items")
                            }
                    }
                Section(header:
                    HStack{ Text("Items") }
                ) {
                    ForEach(newItems, id: \.self){ item in
                        Text("\(item)")
                    }
                }

            }
            .navigationBarTitle("Items List")
            .listStyle(GroupedListStyle())
        }
    }
}

View 2

struct MyListView: View {
    @Binding var newItems: Array = []


    var myArray = [1, 2, 3, 4]

    var body: some View {
        List(myArray, id: \.self ){i in
            HStack{
                Image(systemName: "photo")
                Text("\(i)").font(.headline)
            }.onTapGesture {
                self.newItems.append(i)
                print(self.newItems.count)
            }
        }
    }
}

Any idea how to solve this?

Thanks in advance

Upvotes: 1

Views: 2005

Answers (2)

woneill1701
woneill1701

Reputation: 331

Change:

struct MyListView: View {
        @Binding var newItems: Array = [] 

To:

    struct MyListView: View {
        @Binding var newItems: Array

The problem is that in your MyListView you are replacing the bound array with a different local empty array.

When you pass in a @Binding value you NEVER initialize it in the destination view!

Upvotes: 3

Craig Temple
Craig Temple

Reputation: 545

The problem is that you're passing an array, which is a value type (arrays, strings, dictionaries, structs, etc. are value types). When you pass that array, you're (effectively) passing a new copy of the data in it to the new view. Changes you make there will not affect the array in your first View, and would have to then be passed back into the old View, which is a bit of a mess to keep in sync.

Instead you want to use a reference type (a class), where the variable passed to the new View is actually just a pointer to the same data (the class instance) that the first View's variable was pointing at. Then when ANY View or function works on that variable, it will edit the same instance data.

Super handy for passing data back and forth through multi-view apps. 👍

Only catch is that you cant use the @State wrapper on classes. But there's a fairly simple equivalent for classes.

You need to first create the class as conforming to the protocol ObservableObject , and then declare any of its properties as @Published. Then in your Views you instantiate the classes as @ObservedObjects

So break out your newItems into a class

class NewItems: ObservableObject {
    @Published var items = [Int]()
    // And any other properties you want to pass back and forth.
    // Maybe even a complex Struct (which is a value type, but wrapped in a class)
}

And then in your ContentView, you have to instantiate that class and call its items array a bit differently:

struct ContentView: View {
    @ObservedObject var newItems: NewItems() // New instance of NewItems is created.
    // FYI your array is now accessed by calling newItems.items like so:
    //  newItems.items = [1, 2, 3] 

 var body: some View {
            NavigationView {
                List(){
                    Section(header:
                        HStack{
                            Image(systemName: "plus.square.fill")
                            Text("Find Items")
                        })
                        {
                            // Now you're passing a pointer to the instance of the NewItems class
                            NavigationLink( destination: MyListView(newItems: self.$newItems) ){ 
                                    Text("Add Items")
                                }
                        }
                    Section(header:
                        HStack{ Text("Items") }
                    ) {
                        ForEach(newItems, id: \.self){ item in
                            Text("\(item)")
                        }
                    }

                }
                .navigationBarTitle("Items List")
                .listStyle(GroupedListStyle())
            }
        }
    }


So you're passing an instance of the NewItems class to your MyListView and that means BOTH views will share the same data within that NewItems. Changes in one view will affect the other. Go back and forth all you want, make changes, it will just work.

Here's how you handle the new class instance in MyListView:

struct MyListView: View {
    // We're getting a class now that has your [Int] within it
    @ObservedObject var newItems: NewItems


    var myArray = [1, 2, 3, 4]

    var body: some View {
        List(myArray, id: \.self ){i in
            HStack{
                Image(systemName: "photo")
                Text("\(i)").font(.headline)
            }.onTapGesture {
                self.newItems.append(i)
                // Make sure you call the items array within newItems
                print(self.newItems.items.count)
            }
        }
    }
}

To share the same data between multiple views (or multiple functions, etc) you can simply wrap it in a class. Then changes on any view will be made to the same data within that class instance.

You can even take it one step further and wrap the NewItems instance with @EnvironmentObject, so you don't even have to pass it between the views in your NavigationLinks. More details here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views

Upvotes: 1

Related Questions