17 votes

SwiftUI - Utiliser @Binding avec Core Data NSManagedObject ?

J'essaie de créer un formulaire de modification qui peut prendre une valeur comme @Binding, la modifier et valider la modification. Dans ce cas, je remplis une liste avec des enregistrements Core Data en utilisant le wrapper de la propriété @FetchRequest. Je veux taper sur une ligne pour naviguer de la liste à la vue détaillée, puis sur la vue détaillée, je veux naviguer vers la vue d'édition.

Example image

J'ai essayé de faire cela sans le @Binding et le code se compile mais lorsque je fais une modification, elle n'est pas reflétée sur les écrans précédents. Il semble que je doive utiliser @Binding mais je n'arrive pas à trouver un moyen d'obtenir une instance de NSManagedObject à l'intérieur d'une liste ou d'un ForEach, et de la passer à une vue qui peut l'utiliser comme @Binding.

Vue de la liste

struct TimelineListView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

    // The Timeline class has an `allTimelinesFetchRequest` function that can be used here
    @FetchRequest(fetchRequest: Timeline.allTimelinesFetchRequest()) var timelines: FetchedResults<Timeline>

    @State var openAddModalSheet = false

    var body: some View {

        return NavigationView {
            VStack {
                List {

                    Section(header:
                        Text("Lists")
                    ) {
                        ForEach(self.timelines) { timeline in

                            //  How to I use the timeline in this list as a @Binding?

                            NavigationLink(destination: TimelineDetailView(timeline: $timeline)) {
                                TimelineCell(timeline: timeline)
                            }
                        }
                    }
                    .font(.headline)

                }
                .listStyle(GroupedListStyle())

            }

            .navigationBarTitle(Text("Lists"), displayMode: .inline)

        }

    } // End Body
}

Vue détaillée

struct TimelineDetailView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

    @Binding var timeline: Timeline

    var body: some View {

        List {

            Section {

                NavigationLink(destination: TimelineEditView(timeline: $timeline)) {
                    TimelineCell(timeline: timeline)
                }

            }

            Section {

                Text("Event data here")
                Text("Another event here")
                Text("A third event here")

            }

        }.listStyle(GroupedListStyle())

    }
}

Modifier la vue

struct TimelineEditView: View {

    @Environment(\.managedObjectContext) var managedObjectContext

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State private var newDataValue = ""

    @Binding var timeline: Timeline

    var body: some View {

        return VStack {

            TextField("Data to edit", text: self.$newDataValue)
                .shadow(color: .secondary, radius: 1, x: 0, y: 0)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .onAppear {
                    self.newDataValue = self.timeline.name ?? ""
            }.padding()
            Spacer()
        }

            .navigationBarItems(
                leading:
                Button(action: ({
                    // Dismiss the modal sheet
                    self.newDataValue = ""
                    self.presentationMode.wrappedValue.dismiss()

                })) {
                    Text("Cancel")
                },

                trailing: Button(action: ({

                    self.timeline.name = self.newDataValue

                    do {
                        try self.managedObjectContext.save()
                    } catch {
                        print(error)
                    }

                    // Dismiss the modal sheet
                    self.newDataValue = ""
                    self.presentationMode.wrappedValue.dismiss()

                })) {
                    Text("Done")
                }
        )

    }
}

Je devrais mentionner que la seule raison pour laquelle j'essaie de faire ça est que la modale .sheet() le truc est super bogué.

4voto

nine stones Points 139

@Binding ne fonctionne qu'avec les structs.

Mais les résultats de CoreData sont des objets ( NSManagedObject en adoptant ObservableObject ). Vous devez utiliser @ObservedObject pour s'inscrire aux changements.

0voto

hstdt Points 998

Pseudocode :

// pass value & init child view

List(templates) { template in
    TemplateCell(template: Binding.constant(template)) // init 
}

struct TemplateCell {
    @Binding var template: Template // @Binding for reload cell automatic
}

// TextField + Edit

TextField("Content", text: (Binding($template.content) ?? Binding.constant("")), onEditingChanged: { isEditing in
    // save CoreData here when keyboard hide

}, onCommit: {
    // press enter, you can insert something

})

// combine
.onReceive(self.template.objectWillChange) { _ in
    // can do some thing
}

0voto

Pour mettre en œuvre la fonctionnalité de création et d'édition avec Core Data, il est préférable d'utiliser l'outil de création et de modification des données. contextes d'objets gérés imbriqués . Si nous injectons un contexte d'objet géré enfant, dérivé du contexte de vue principal, ainsi que l'objet géré en cours de création ou de modification qui est associé à un contexte enfant, nous obtenons un espace sûr où nous pouvons apporter des modifications et les abandonner si nécessaire sans altérer le contexte qui pilote notre interface utilisateur.

    let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    childContext.parent = viewContext
    let childItem = childContext.object(with: objectID) as! Item
    return ItemHost(item: childItem)
        .environment(\.managedObjectContext, childContext)

Une fois que nous avons terminé nos modifications, il nous suffit de sauvegarder le contexte enfant et les modifications seront poussées vers le contexte de vue principal et peuvent être sauvegardées immédiatement ou plus tard, en fonction de votre architecture. Si nous ne sommes pas satisfaits de nos changements, nous pouvons les abandonner en appelant refresh(_:mergeChanges:) sur notre contexte enfant tout en passant dans notre objet enfant.

    childContext.refresh(item, mergeChanges: false)

En ce qui concerne la question de la liaison des objets gérés avec les vues SwiftUI, une fois que notre objet enfant est injecté dans notre formulaire d'édition, nous pouvons lier ses propriétés directement aux contrôles SwiftUI. Ceci est possible car NSManagedObject est conforme à ObservableObject protocole. Tout ce que nous avons à faire est de marquer une propriété qui contient une référence à notre objet enfant avec @ObservedObject et nous recevons ses éditeurs. La seule complication ici est qu'il y a souvent des incompatibilités de type. Par exemple, les objets gérés stockent les chaînes de caractères en tant que String? mais TextField s'attend à ce que String . Pour contourner cela, nous pouvons étendre la structure de Binding et introduire des proxies comme suit.

    extension Binding {
        func optionalProxy<Wrapped>() -> Binding<Wrapped>? where Value == Optional<Wrapped> {
            guard let value = self.wrappedValue else { return nil }

            return Binding<Wrapped>(
                get: {
                    value
                },
                set: {
                    self.wrappedValue = $0
                }
            )
        }
    }

Nous pouvons maintenant utiliser nos liaisons, à condition que l'attribut name ait une chaîne vide par défaut définie dans le modèle d'objet géré, sinon cela se plantera.

    TextField("Title", text: $item.title.optionalProxy()!)

De cette façon, nous pouvons mettre en œuvre proprement la philosophie de SwiftUI qui consiste à ne pas partager d'état. J'ai fourni un exemple de projet pour de plus amples informations.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X