Làm cách nào để cập nhật @FetchRequest, khi một Thực thể có liên quan thay đổi trong SwiftUI?


13

Trong SwiftUI Viewtôi có một Listcơ sở dựa trên việc @FetchRequesthiển thị dữ liệu của một Primarythực thể và thực thể thông qua mối quan hệ được kết nối Secondary. Và ViewListđược cập nhật chính xác, khi tôi thêm một Primarythực thể mới với một thực thể thứ cấp mới có liên quan.

Vấn đề là, khi tôi cập nhật Secondarymục được kết nối trong chế độ xem chi tiết, cơ sở dữ liệu sẽ được cập nhật, nhưng những thay đổi không được phản ánh trong PrimaryDanh sách. Rõ ràng, @FetchRequestkhông được kích hoạt bởi những thay đổi trong Chế độ xem khác.

Khi tôi thêm một mục mới trong chế độ xem chính sau đó, mục đã thay đổi trước đó cuối cùng sẽ được cập nhật.

Như một giải pháp thay thế, tôi cũng cập nhật thêm một thuộc tính của Primarythực thể trong chế độ xem chi tiết và các thay đổi lan truyền chính xác đến PrimaryChế độ xem.

Câu hỏi của tôi là: Làm cách nào tôi có thể buộc cập nhật tất cả các @FetchRequestsdữ liệu liên quan đến SwiftUI Core? Đặc biệt, khi tôi không có quyền truy cập trực tiếp vào các thực thể liên quan / @Fetchrequests?

Cấu trúc dữ liệu

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

Không có ích, xin lỗi. Nhưng tôi đang gặp vấn đề tương tự. Chế độ xem chi tiết của tôi có một tham chiếu đến đối tượng chính được chọn. Nó hiển thị một danh sách các đối tượng thứ cấp. Tất cả các chức năng CRUD hoạt động chính xác trong Dữ liệu lõi nhưng không được phản ánh trong giao diện người dùng. Rất thích có thêm thông tin về điều này.
PJayRushton

Bạn đã thử sử dụng ObservableObject?
kdion4891

Tôi đã thử sử dụng @ObservedObject var sơ cấp: Chính trong chế độ xem chi tiết. Nhưng những thay đổi không lan truyền trở lại vào chế độ xem chính.
Bjorn B.

Câu trả lời:


15

Bạn cần một Nhà xuất bản sẽ tạo sự kiện về các thay đổi trong ngữ cảnh và một số biến trạng thái trong chế độ xem chính để buộc xây dựng lại chế độ xem khi nhận sự kiện từ nhà xuất bản đó.
Quan trọng: biến trạng thái phải được sử dụng trong mã trình xây dựng chế độ xem, nếu không công cụ kết xuất sẽ không biết rằng có gì đó đã thay đổi.

Đây là sửa đổi đơn giản của phần bị ảnh hưởng trong mã của bạn, cung cấp hành vi mà bạn cần.

@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")
        }
    )
}

Đây là hy vọng Apple cải thiện tích hợp Dữ liệu lõi <-> SwiftUI trong tương lai. Trao tiền thưởng cho câu trả lời tốt nhất được cung cấp. Cảm ơn Asperi.
Rễ Darrell

Cảm ơn bạn vì câu trả lời! Nhưng @FetchRequest sẽ phản ứng với những thay đổi trong cơ sở dữ liệu. Với giải pháp của bạn, Chế độ xem sẽ được cập nhật với mỗi lần lưu trên cơ sở dữ liệu, bất kể các mục liên quan. Câu hỏi của tôi là làm thế nào để @FetchRequest phản ứng với những thay đổi liên quan đến quan hệ cơ sở dữ liệu. Giải pháp của bạn cần một người đăng ký thứ hai (Thông báo Trung tâm) song song với @FetchRequest. Ngoài ra, người ta phải sử dụng một trình kích hoạt giả mạo bổ sung `+ (self.refreshing?" ":" ")`. Có lẽ một @Fetchrequest không phải là một giải pháp phù hợp?
Bjorn B.

Có, bạn đúng, nhưng yêu cầu tìm nạp như được tạo trong ví dụ không bị ảnh hưởng bởi những thay đổi được thực hiện gần đây, đó là lý do tại sao nó không được cập nhật / tải lại. Có thể có một lý do để xem xét các tiêu chí yêu cầu tìm nạp khác nhau, nhưng đó là câu hỏi khác nhau.
Asperi

2
@Asperi Tôi chấp nhận câu trả lời của bạn. Như bạn đã nói, vấn đề nằm ở chỗ nào đó với công cụ kết xuất để nhận ra bất kỳ thay đổi nào. Sử dụng một tham chiếu đến một đối tượng thay đổi không đủ. Một biến đã thay đổi phải được sử dụng trong Chế độ xem. Trong bất kỳ phần nào của cơ thể. Ngay cả được sử dụng trên một nền tảng trong Danh sách sẽ hoạt động. Tôi sử dụng một RefreshView(toggle: Bool)với một EmptyView trong cơ thể của nó. Sử dụng List {...}.background(RefreshView(toggle: self.refreshing))sẽ làm việc.
Bjorn B.

Tôi đã tìm thấy cách tốt hơn để buộc làm mới / tải lại danh sách, nó được cung cấp trong SwiftUI: Danh sách không tự động cập nhật sau khi xóa tất cả các mục Thực thể dữ liệu cốt lõi . Chỉ trong trường hợp.
Asperi

1

Tôi đã cố chạm vào đối tượng chính trong chế độ xem chi tiết như thế này:

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

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

Sau đó, danh sách chính sẽ cập nhật. Nhưng quan điểm chi tiết phải biết về đối tượng cha. Điều này sẽ hoạt động, nhưng đây có lẽ không phải là cách SwiftUI hay Kết hợp ...

Biên tập:

Dựa trên cách giải quyết ở trên, tôi đã sửa đổi dự án của mình với chức năng lưu toàn cầu (ManagedObject :). Điều này sẽ chạm vào tất cả các Thực thể có liên quan, do đó cập nhật tất cả các @ FetchRequest của có liên quan.

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()
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.