SwiftUI'de ilgili bir Varlık değiştiğinde @FetchRequest nasıl güncellenir?


13

Bir SwiftUI olarak Viewi sahip Listgöre @FetchRequestbir verilerini gösteren Primarybir varlık ve ilişki üzerinden, Secondaryvarlık. ViewVe onun Listyeni bir eklediğinizde, doğru güncellenir PrimaryYeni bir ilgili ikincil varlık ile varlık.

Sorun, bağlı Secondaryöğeyi bir ayrıntı görünümünde güncelleştirdiğimde, veritabanı güncelleştirilir, ancak değişiklikler PrimaryListeye yansıtılmaz . Açıkçası, @FetchRequestbaşka bir Görünümdeki değişiklikler tarafından tetiklenmez.

Daha sonra birincil görünümde yeni bir öğe eklediğimde, daha önce değiştirilen öğe nihayet güncellenir.

Geçici bir çözüm olarak, Primaryayrıntı görünümünde varlığın bir özniteliğini de güncelleştiririm ve değişiklikler PrimaryGörünüm'e doğru şekilde yayılır .

Benim sorum: @FetchRequestsSwiftUI Temel Verileri ile ilgili tüm güncellemeleri nasıl zorlayabilirim ? Özellikle, ilgili varlıklara doğrudan erişimim olmadığında / @Fetchrequests?

Veri yapısı

import SwiftUI

extension Primary: Identifiable {}

// Primary View

struct PrimaryListView: View {
    @Environment(\.managedObjectContext) var context

    @FetchRequest(
        entity: Primary.entity(),
        sortDescriptors: [NSSortDescriptor(key: "primaryName", ascending: true)]
    )
    var fetchedResults: FetchedResults<Primary>

    var body: some View {
        List {
            ForEach(fetchedResults) { primary in
                NavigationLink(destination: SecondaryView(primary: primary)) {
                VStack(alignment: .leading) {
                    Text("\(primary.primaryName ?? "nil")")
                    Text("\(primary.secondary?.secondaryName ?? "nil")").font(.footnote).foregroundColor(.secondary)
                }
                }
            }
        }
        .navigationBarTitle("Primary List")
        .navigationBarItems(trailing:
            Button(action: {self.addNewPrimary()} ) {
                Image(systemName: "plus")
            }
        )
    }

    private func addNewPrimary() {
        let newPrimary = Primary(context: context)
        newPrimary.primaryName = "Primary created at \(Date())"
        let newSecondary = Secondary(context: context)
        newSecondary.secondaryName = "Secondary built at \(Date())"
        newPrimary.secondary = newSecondary
        try? context.save()
    }
}

struct PrimaryListView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

        return NavigationView {
            PrimaryListView().environment(\.managedObjectContext, context)
        }
    }
}

// Detail View

struct SecondaryView: View {
    @Environment(\.presentationMode) var presentationMode

    var primary: Primary

    @State private var newSecondaryName = ""

    var body: some View {
        VStack {
            TextField("Secondary name:", text: $newSecondaryName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
                .onAppear {self.newSecondaryName = self.primary.secondary?.secondaryName ?? "no name"}
            Button(action: {self.saveChanges()}) {
                Text("Save")
            }
            .padding()
        }
    }

    private func saveChanges() {
        primary.secondary?.secondaryName = newSecondaryName

        // TODO: ❌ workaround to trigger update on primary @FetchRequest
        primary.managedObjectContext.refresh(primary, mergeChanges: true)
        // primary.primaryName = primary.primaryName

        try? primary.managedObjectContext?.save()
        presentationMode.wrappedValue.dismiss()
    }
}

Yararlı değil üzgünüm. Ama ben aynı sorunu yaşıyorum. Ayrıntı görünümümde seçilen birincil nesneye başvuru var. İkincil nesnelerin bir listesini gösterir. Tüm CRUD işlevleri Temel Veriler'de düzgün çalışır, ancak kullanıcı arayüzüne yansımaz. Bu konuda daha fazla bilgi almak isterim.
PJayRushton

Kullanmayı denedin ObservableObjectmi?
kdion4891

Ayrıntı görünümünde @ObservedObject var birincil: Birincil kullanmayı denedim. Ancak değişiklikler yeniden birincil görünüme yayılmaz.
Björn

Yanıtlar:


15

Bu yayıncıdan alma olayında yeniden oluşturmaya zorlamak için bağlamdaki değişiklikler ve birincil görünümdeki bazı durum değişkenleri hakkında etkinlik oluşturacak bir Yayıncıya ihtiyacınız var.
Önemli: Devlet değişken gerekir aksi render motoru değişti şey bilemeyiz, görünüm oluşturucu kodunda kullanılabilir.

Kodunuzun etkilenen kısmının, ihtiyacınız olan davranışı veren basit bir şekilde değiştirilmesi.

@State private var refreshing = false
private var didSave =  NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)

var body: some View {
    List {
        ForEach(fetchedResults) { primary in
            NavigationLink(destination: SecondaryView(primary: primary)) {
                VStack(alignment: .leading) {
                    // below use of .refreshing is just as demo,
                    // it can be use for anything
                    Text("\(primary.primaryName ?? "nil")" + (self.refreshing ? "" : ""))
                    Text("\(primary.secondary?.secondaryName ?? "nil")").font(.footnote).foregroundColor(.secondary)
                }
            }
            // here is the listener for published context event
            .onReceive(self.didSave) { _ in
                self.refreshing.toggle()
            }
        }
    }
    .navigationBarTitle("Primary List")
    .navigationBarItems(trailing:
        Button(action: {self.addNewPrimary()} ) {
            Image(systemName: "plus")
        }
    )
}

Apple'ın gelecekte Core Data <-> SwiftUI entegrasyonunu geliştirmesini umuyoruz. Ödülün verilen en iyi cevaba verilmesi. Teşekkürler Asperi.
Darrell Root

Cevabınız için teşekkür ederim! Ancak @FetchRequest veritabanındaki değişikliklere tepki vermelidir. Çözümünüzle, Görünüm, ilgili öğelere bakılmaksızın veritabanındaki her kaydetmeyle güncellenecektir. Benim sorum veritabanı ilişkileri içeren değişikliklere tepki @FetchRequest almak oldu. Çözümünüzün @FetchRequest öğesine paralel olarak ikinci bir aboneye (NotificationCenter) ihtiyacı var. Ayrıca ek bir sahte tetikleyici `+ (self.refreshing?" ":" ")` Kullanmalıdır. Belki de @Fetchrequest uygun bir çözüm değildir?
Björn

Evet, haklısınız, ancak örnekte oluşturulduğu gibi getirme isteği son zamanlarda yapılan değişikliklerden etkilenmez, bu nedenle güncellenmez / yeniden yüklenmez. Farklı getirme isteği ölçütlerini dikkate almak için bir neden olabilir, ancak bu farklı bir sorudur.
Asperi

2
@Asperi Cevabınızı kabul ediyorum. Belirttiğiniz gibi, sorun herhangi bir değişikliği tanımak için oluşturma motorunda bir şekilde yatmaktadır. Değiştirilmiş bir nesneye başvuru kullanmak yeterli değildir. Görünümde değiştirilmiş bir değişken kullanılmalıdır. Vücudun herhangi bir yerinde. Listede bir arka planda kullanılan bile çalışır. RefreshView(toggle: Bool)Bedeninde tek bir EmptyView bulunan bir kullanıyorum . Kullanmak List {...}.background(RefreshView(toggle: self.refreshing))işe yarayacak.
Björn

Liste yenilemeyi / yeniden getirmeyi zorlamanın daha iyi bir yolunu buldum, SwiftUI'de sağlandı: Liste, tüm Temel Veri Varlığı girişlerini sildikten sonra otomatik olarak güncellenmiyor . Her ihtimale karşı.
Asperi

1

Ayrıntı görünümünde birincil nesneye şu şekilde dokunmaya çalıştım:

// TODO: ❌ workaround to trigger update on primary @FetchRequest

if let primary = secondary.primary {
   secondary.managedObjectContext?.refresh(primary, mergeChanges: true)
}

Ardından birincil liste güncellenir. Ancak ayrıntı görünümü üst nesne hakkında bilgi sahibi olmalıdır. Bu işe yarayacak, ancak muhtemelen SwiftUI veya Birleştir yolu değil ...

Düzenle:

Yukarıdaki geçici çözümü temel alarak, projemi bir global save (manageObject :) işlevi ile değiştirdim. Bu, ilgili tüm Varlıklara dokunacak ve böylece ilgili tüm @ FetchRequest'lerin güncellenecektir.

import SwiftUI
import CoreData

extension Primary: Identifiable {}

// MARK: - Primary View

struct PrimaryListView: View {
    @Environment(\.managedObjectContext) var context

    @FetchRequest(
        sortDescriptors: [
            NSSortDescriptor(keyPath: \Primary.primaryName, ascending: true)]
    )
    var fetchedResults: FetchedResults<Primary>

    var body: some View {
        print("body PrimaryListView"); return
        List {
            ForEach(fetchedResults) { primary in
                NavigationLink(destination: SecondaryView(secondary: primary.secondary!)) {
                    VStack(alignment: .leading) {
                        Text("\(primary.primaryName ?? "nil")")
                        Text("\(primary.secondary?.secondaryName ?? "nil")")
                            .font(.footnote).foregroundColor(.secondary)
                    }
                }
            }
        }
        .navigationBarTitle("Primary List")
        .navigationBarItems(trailing:
            Button(action: {self.addNewPrimary()} ) {
                Image(systemName: "plus")
            }
        )
    }

    private func addNewPrimary() {
        let newPrimary = Primary(context: context)
        newPrimary.primaryName = "Primary created at \(Date())"
        let newSecondary = Secondary(context: context)
        newSecondary.secondaryName = "Secondary built at \(Date())"
        newPrimary.secondary = newSecondary
        try? context.save()
    }
}

struct PrimaryListView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

        return NavigationView {
            PrimaryListView().environment(\.managedObjectContext, context)
        }
    }
}

// MARK: - Detail View

struct SecondaryView: View {
    @Environment(\.presentationMode) var presentationMode

    var secondary: Secondary

    @State private var newSecondaryName = ""

    var body: some View {
        print("SecondaryView: \(secondary.secondaryName ?? "")"); return
        VStack {
            TextField("Secondary name:", text: $newSecondaryName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()
                .onAppear {self.newSecondaryName = self.secondary.secondaryName ?? "no name"}
            Button(action: {self.saveChanges()}) {
                Text("Save")
            }
            .padding()
        }
    }

    private func saveChanges() {
        secondary.secondaryName = newSecondaryName

        // save Secondary and touch Primary
        (UIApplication.shared.delegate as! AppDelegate).save(managedObject: secondary)

        presentationMode.wrappedValue.dismiss()
    }
}

extension AppDelegate {
    /// save and touch related objects
    func save(managedObject: NSManagedObject) {

        let context = persistentContainer.viewContext

        // if this object has an impact on related objects, touch these related objects
        if let secondary = managedObject as? Secondary,
            let primary = secondary.primary {
            context.refresh(primary, mergeChanges: true)
            print("Primary touched: \(primary.primaryName ?? "no name")")
        }

        saveContext()
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.