जब स्विफ्टयूआई में संबंधित एंटिटी बदलती है तो @FetchRequest को कैसे अपडेट करें?


13

एक स्विफ्टयूआई में Viewमेरे पास एक इकाई का डेटा दिखाने और रिश्ते से जुड़ी इकाई के माध्यम से Listआधारित है । और उसके सही ढंग से अद्यतन किया जाता है, जब मैं एक नया जोड़ने के लिए एक नया संबंधित माध्यमिक संस्था के साथ इकाई।@FetchRequestPrimarySecondaryViewListPrimary

समस्या यह है, जब मैं कनेक्ट किए गए Secondaryआइटम को एक विस्तृत दृश्य में अपडेट करता हूं , डेटाबेस अपडेट हो जाता है, लेकिन सूची में परिवर्तन प्रतिबिंबित नहीं होते हैं Primary। जाहिर है, @FetchRequestकिसी अन्य दृश्य में परिवर्तन से ट्रिगर नहीं होता है।

जब मैं उसके बाद प्राथमिक दृश्य में एक नया आइटम जोड़ता हूं, तो पहले से परिवर्तित आइटम अंत में अपडेट हो जाता है।

वर्कअराउंड के रूप में, मैं अतिरिक्त रूप Primaryसे विस्तार दृश्य में इकाई की एक विशेषता को अद्यतन करता हूं और परिवर्तन सही तरीके से Primaryदृश्य में प्रचारित करता है ।

मेरा सवाल है: मैं @FetchRequestsस्विफ्टयूआई कोर डेटा से संबंधित सभी पर अपडेट कैसे लागू कर सकता हूं ? विशेष रूप से, जब मेरे पास संबंधित संस्थाओं के लिए कोई सीधी पहुंच नहीं है / @Fetchrequests?

डेटा संरचना

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()
    }
}

मददगार नहीं, सॉरी। लेकिन मैं इसी मुद्दे पर चल रहा हूं। मेरे विवरण दृश्य में चयनित प्राथमिक ऑब्जेक्ट का संदर्भ है। यह द्वितीयक वस्तुओं की सूची दिखाता है। कोर डेटा में सभी CRUD ठीक से काम करते हैं लेकिन UI में परिलक्षित नहीं होते हैं। इस बारे में अधिक जानकारी प्राप्त करना पसंद करेंगे।
PJayRushton

क्या आपने प्रयोग करने की कोशिश की है ObservableObject?
kdion4891

मैंने @ObservedObject var प्राथमिक का उपयोग करने का प्रयास किया: विस्तार से देखने में प्राथमिक। लेकिन बदलाव प्राथमिक दृष्टिकोण में वापस नहीं फैलते हैं।
ब्योर्न बी।

जवाबों:


15

आपको एक प्रकाशक की आवश्यकता है जो संदर्भ में परिवर्तन के बारे में घटना उत्पन्न करेगा और उस प्रकाशक से प्राप्त घटना पर फिर से देखने के लिए मजबूर करने के लिए प्राथमिक दृश्य में कुछ राज्य चर।
महत्वपूर्ण: राज्य कोड का उपयोग बिल्डर कोड को देखने में किया जाना चाहिए, अन्यथा रेंडरिंग इंजन को पता नहीं चलेगा कि कुछ बदल गया है।

यहां आपके कोड के प्रभावित हिस्से का सरल संशोधन है, जो आपको आवश्यक व्यवहार देता है।

@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 भविष्य में कोर डेटा <-> स्विफ्टयूआई एकीकरण में सुधार करेगा। सबसे अच्छा जवाब प्रदान करने के लिए इनाम देने। धन्यवाद अस्पररी।
डैरेल रूट

आपके उत्तर के लिए धन्यवाद! लेकिन @FetchRequest को डेटाबेस में बदलाव के लिए प्रतिक्रिया देनी चाहिए। आपके समाधान के साथ, व्यू को डेटाबेस में प्रत्येक सेव के साथ अपडेट किया जाएगा, भले ही इसमें शामिल आइटम हों। मेरा सवाल यह था कि डेटाबेस संबंधों से जुड़े परिवर्तनों पर प्रतिक्रिया के लिए @FetchRequest कैसे प्राप्त करें। आपके समाधान को @FetchRequest के समानांतर एक दूसरे सब्सक्राइबर (NotificationCenter) की आवश्यकता है। इसके अलावा एक अतिरिक्त नकली ट्रिगर का उपयोग करना पड़ता है `+ (self.refreshing?" ":" ")`। शायद @Fetchrequest स्वयं एक उपयुक्त समाधान नहीं है?
ब्योर्न बी।

हां, आप सही हैं, लेकिन जैसा कि यह उदाहरण में बनाया गया है, भ्रूण का अनुरोध उन परिवर्तनों से प्रभावित नहीं होता है, जो हाल ही में किए गए हैं, इसीलिए इसे अपडेट / रिफेट नहीं किया गया है। हो सकता है कि विभिन्न अनुरोधों पर विचार करने का कोई कारण हो, लेकिन यह अलग सवाल है।
अस्पररी

2
@Asperi मैं आपका जवाब स्वीकार करता हूं। जैसा कि आपने कहा, समस्या किसी भी परिवर्तन को पहचानने के लिए रेंडरिंग इंजन के साथ किसी तरह निहित है। एक बदले हुए ऑब्जेक्ट के संदर्भ का उपयोग करना पर्याप्त नहीं है। दृश्य में परिवर्तित चर का उपयोग किया जाना चाहिए। शरीर के किसी भी हिस्से में। यहां तक ​​कि सूची पर एक पृष्ठभूमि पर इस्तेमाल किया काम करेगा। मैं RefreshView(toggle: Bool)इसके शरीर में एक सिंगल एमप्टी व्यू के साथ उपयोग करता हूं । का उपयोग करते हुए List {...}.background(RefreshView(toggle: self.refreshing))काम करेंगे।
Björn B.

मैंने सूची को ताज़ा / रीफ़्रेश करने के लिए बेहतर तरीका खोज लिया है, यह SwiftUI में प्रदान किया गया है: सभी कोर डेटा एंटिटी प्रविष्टियों को हटाने के बाद सूची स्वचालित रूप से अपडेट नहीं होती है । शायद ज़रुरत पड़े।
असेरी

1

मैंने इस तरह से विस्तार में प्राथमिक वस्तु को छूने की कोशिश की:

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

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

फिर प्राथमिक सूची अपडेट होगी। लेकिन विस्तार से देखने के लिए मूल वस्तु के बारे में जानना होगा। यह काम करेगा, लेकिन यह शायद स्विफ्टयूआई या कंबाइन तरीका नहीं है ...

संपादित करें:

उपर्युक्त वर्कअराउंड के आधार पर, मैंने अपनी परियोजना को एक वैश्विक बचत (मैनेजऑबजेक्ट :) फ़ंक्शन के साथ संशोधित किया। यह सभी संबंधित संस्थाओं को स्पर्श करेगा, इस प्रकार सभी प्रासंगिक @ FetchRequest को अपडेट करेगा।

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()
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.