Reputation: 139
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
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
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 NavigationLink
s. More details here: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views
Upvotes: 1