0

This is the next part of that question.

I've got the follow code.

The initial view of the app:

struct InitialView : View {
    var body: some View {
        Group { 
            PresentationButton(destination: ObjectsListView()) {
                Text("Show ListView")
            }
            PresentationButton(destination: AnotherObjectsListView()) {
                Text("Show AnotherListView")
            }
        }
    }
}

The list view of the objects:

struct ObjectsListView : View {

    @Environment(\.myObjectsStore.objects) var myObjectsStores: Places

    var body: some View {
        Group {    
            Section {
                ForEach(myObjectsStore.objects) { object in
                    NavigationLink(destination: ObjectDetailView(object: object)) {
                        ObjectCell(object: object)
                    }
                }
            }
            Section {
                // this little boi
                PresentationButton(destination: ObjectDetailView(objectToEdit: MyObject(store: myObjectsStore))) {
                    Text("Add New Object")
                }
            }
        }
    }
}

The detail view:

struct ObjectsDetailView : View {

    @Binding var myObject: MyObject    

    var body: some View {
        Text("\(myObject.title)")
    }
}

So the problem is quite complex.

  1. The ObjectsListView creates instance of the MyObject(store: myObjectsStore) on itself initialization while computing body.
  2. The MyObject object is setting its store property on itself initialization, since it should know is it belongs to myObjectsStore or to anotherMyObjectsStore.
  3. The myObjectsStore are @BindableObjects since their changes are managing by SwiftUI itself.

So this behavior ends up that I've unexpected MyObject() initializations since the Views are computing itself. Like:

  • First MyObject creates on the ObjectsListView initialization.
  • Second MyObject creates on its PresentationButton pressing (the expected one).
  • Third (any sometimes comes even fourth) MyObject creates on dismissing ObjectsDetailView.

So I can't figure what pattern should I use this case to create only one object?

The only thing that I'd come to is to make the follow code:

struct ObjectsListView : View {

    @Environment(\.myObjectsStore.objects) var myObjectsStores: Places

    @State var buttonPressed = false

    var body: some View {
        Group {    
            if buttonPressed {
                ObjectDetailView(objectToEdit: MyObject(store: myObjectsStore))
            } else {

                Section {
                    ForEach(myObjectsStore.objects) { object in
                        NavigationLink(destination: ObjectDetailView(object: object)) {
                            ObjectCell(object: object)
                        }
                    }
                }
                Section {
                    Button(action: {
                        self.buttonPressed.toggle()
                    }) {
                        Text("Add New Object")
                    }
                }
            }
        }
    }
}

Which simply redraw ObjectsListView to detail view conditionally. But it's completely out of iOS guidelines. So how to create the Only One object for another view in SwiftUI?

UPD: Here's the project that represents the bug with Object duplication. I'm still have no idea why the objects are duplicating in this case. But at least I know the reason yet. And the reason is this line:

@Environment(\.myObjectsStore.objects) var myObjectsStores: Places

I've tried to share my model with this wrapper to make it available in every single view (including Modal one) without passing them as an arg to the new view initializer, which are unavailable by the other ways, like @EnvironmentObject wrapper. And for some reason @Environment(\.keyPath) wrapper makes duplications.

So I'd simply replace all variables from Environment(\.) to ObjectBinding and now everything works well.

Yaroslav Y.
  • 327
  • 3
  • 11
  • 1
    I just tested this out with dummy objects, and I saw one initialization, and it was when the view's body was calculated. I did not see another initialization when the `PresentationButton` was tapped or when the view was dismissed. Are you sure the object is initialized multiple times? How do you know (print statement in object's `init`?)? Are the extra objects duplicates of the one you want to create or are they different? – RPatel99 Jul 03 '19 at 14:56
  • @RPatel99 Thanks to you I've found the solution. Thank you for your support! I'd update the question with the example project in which first commit are represents the duplication bug, and second shows fix. – Yaroslav Y. Jul 03 '19 at 20:39

1 Answers1

0

I've found the solution to this.

Here's the project repo that represents the bug with Object duplication and the version that fix this. I'm still have no idea how objects have been duplicate in that case. But I figured out why. It happens because this line:

@Environment(\.myObjectsStore.objects) var myObjectsStores: MyObjectsStore

I've used @Environment(\.key) to connect my model to each view in the navigation stack including Modal one, which are unavailable by the other ways provided in SwiftUI, e.g.: @State, @ObjectBinding, @EnvironmentObject. And for some reason @Environment(\.key) wrapper produce these duplications.

So I'd simply replace all variables from @Environment(\.) to @ObjectBinding and now almost everything works well.

Note: The code is in the rep is still creates one additional object by each workflow walkthrough. So it creates two objects totally instead of one. This behavior could be fixed by way provided in this answer.

Yaroslav Y.
  • 327
  • 3
  • 11