SwiftUI giữ tham chiếu đến đối tượng dữ liệu lõi bị xóa gây ra sự cố


8

Tôi thấy không thể sử dụng dữ liệu lõi với SwiftUI, vì khi tôi chuyển dữ liệu lõi đến biến đối tượng được quan sát, chế độ xem liên kết điều hướng sẽ giữ một tham chiếu đến đối tượng ngay cả khi chế độ xem biến mất, vì vậy ngay khi tôi xóa đối tượng từ bối cảnh ứng dụng gặp sự cố, không có thông báo lỗi.

Tôi đã xác nhận điều này bằng cách gói biến đối tượng dữ liệu lõi thành mô hình khung nhìn làm tùy chọn, sau đó đặt đối tượng thành không ngay sau hành động xóa ngữ cảnh và ứng dụng hoạt động tốt, nhưng đây không phải là giải pháp vì tôi cần đối tượng dữ liệu lõi để ràng buộc với các quan điểm ui nhanh chóng và là nguồn gốc của sự thật. Làm thế nào là giả sử để làm việc? Tôi thực sự không thể làm bất cứ điều gì phức tạp từ xa với SwiftUI.

Tôi đã thử gán đối tượng dữ liệu cốt lõi được truyền cho một @State tùy chọn, nhưng điều này không hoạt động. Tôi không thể sử dụng @Binding vì nó là một đối tượng được tìm nạp. Và tôi không thể sử dụng một biến, vì các điều khiển swiftui yêu cầu các ràng buộc. Việc sử dụng @ObservedObject chỉ có ý nghĩa, nhưng điều này không thể là một tùy chọn, có nghĩa là khi đối tượng được gán cho nó bị xóa, ứng dụng gặp sự cố, vì tôi không thể đặt nó thành không.

Đây là đối tượng dữ liệu cốt lõi, là một đối tượng có thể quan sát theo mặc định:

class Entry: NSManagedObject, Identifiable {

    @NSManaged public var date: Date
}

Đây là một khung nhìn chuyển một đối tượng nhập dữ liệu cốt lõi sang một khung nhìn khác.

struct JournalView: View {

    @Environment(\.managedObjectContext) private var context

    @FetchRequest(
        entity: Entry.entity(),
        sortDescriptors: [],
        predicate: nil,
        animation: .default
    ) var entries: FetchedResults<Entry>

    var body: some View {
        NavigationView {
            List {
                ForEach(entries.indices) { index in
                    NavigationLink(destination: EntryView(entry: self.entries[index])) {
                        Text("Entry")
                    }
                }.onDelete { indexSet in
                    for index in indexSet {
                        self.context.delete(self.entries[index])
                    }
                }
            }
        }
    }
}

Bây giờ đây là chế độ xem truy cập tất cả các thuộc tính từ đối tượng nhập dữ liệu cốt lõi đã được truyền vào. Một lần, tôi xóa mục này, từ bất kỳ chế độ xem nào, nó vẫn được tham chiếu ở đây và khiến ứng dụng bị sập ngay lập tức. Tôi tin rằng điều này cũng có liên quan đến Liên kết Điều hướng khởi tạo tất cả chế độ xem đích trước khi chúng được truy cập. Mà không có lý do tại sao nó sẽ làm điều đó. Đây có phải là một lỗi, hoặc có một cách tốt hơn để đạt được điều này?

Tôi thậm chí đã thử thực hiện xóa onDisappear nhưng không thành công. Ngay cả khi tôi thực hiện xóa khỏi Nhật ký, nó vẫn sẽ bị sập vì NavigationLink vẫn đang tham chiếu đến đối tượng. Điều thú vị là nó sẽ không bị sập nếu xóa một NavigationLink chưa được nhấp vào.

struct EntryView: View {

    @Environment(\.managedObjectContext) private var context
    @Environment(\.presentationMode) private var presentationMode

    @ObservedObject var entry: Entry

    var body: some View {
        Form {

            DatePicker(selection: $entry.date) {
                Text("Date")
            }

            Button(action: {
                self.context.delete(self.entry)
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Delete")
            }
        }
    }
}

CẬP NHẬT

Sự cố đang đưa tôi đến lần sử dụng đầu tiên trong EntryView và đọc Chủ đề 1: EXC_BAD_INXD (code = EXC_I386_INVOP, subcode = 0x0) .. đó là thông điệp duy nhất được ném.

Công việc duy nhất tôi có thể nghĩ đến là thêm một thuộc tính vào đối tượng dữ liệu cốt lõi "isDelatted" và đặt nó thành true thay vì cố gắng xóa khỏi ngữ cảnh. Sau đó, khi ứng dụng được thoát, hoặc khi khởi chạy, tôi có thể xóa và xóa tất cả các mục bị xóa? Không lý tưởng, và muốn tìm hiểu những gì nó sai ở đây, vì có vẻ như tôi không làm gì khác với mẫu MasterDetailApp, dường như hoạt động.


thật là rắc rối Có bản cập nhật nào trên @SybrSyn này không?!
Fattie

Câu trả lời:


4

Tôi về cơ bản đã có cùng một vấn đề. Có vẻ như SwiftUI tải mọi chế độ xem ngay lập tức, do đó, chế độ xem đã được tải với Thuộc tính của Đối tượng CoreData hiện có. Nếu bạn xóa nó trong Chế độ xem nơi một số dữ liệu được truy cập qua @ObservedObject, nó sẽ bị sập.

Cách giải quyết của tôi:

  1. Hành động xóa - đã hoãn, nhưng đã kết thúc thông qua Trung tâm thông báo
    Button(action: {
      //Send Message that the Item  should be deleted
       NotificationCenter.default.post(name: .didSelectDeleteDItem, object: nil)

       //Navigate to a view where the CoreDate Object isn't made available via a property wrapper
        self.presentationMode.wrappedValue.dismiss()
      })
      {Text("Delete Item")}

Bạn cần xác định một Thông báo. Tên, như:

extension Notification.Name {

    static var didSelectDeleteItem: Notification.Name {
        return Notification.Name("Delete Item")
    }
}
  1. Trên Chế độ xem phù hợp, hãy tìm Thông báo Xóa

// Receive Message that the Disease should be deleted
    .onReceive(NotificationCenter.default.publisher(for: .didSelectDeleteDisease)) {_ in

        //1: Dismiss the View (IF It also contains Data from the Item!!)
        self.presentationMode.wrappedValue.dismiss()

        //2: Start deleting Disease - AFTER view has been dismissed
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(1)) {self.dataStorage.deleteDisease(id: self.diseaseDetail.id)}
    }
  1. Hãy an toàn trên Chế độ xem của bạn nơi có một số yếu tố CoreData được truy cập - Kiểm tra isFault!

    VStack{
         //Important: Only display text if the disease item is available!!!!
           if !diseaseDetail.isFault {
                  Text (self.diseaseDetail.text)
            } else { EmptyView() }
    }

Một chút hacky, nhưng điều này làm việc cho tôi.


Tuyệt vời, cảm ơn bạn cho giải pháp này. Tôi sẽ phải cố gắng với điều này. Cách giải quyết mà tôi đã đưa ra trong lúc này là thêm một thuộc tính cho thực thể có tên "inTrash" và đặt nó thành đúng khi xóa, lọc rác trong các yêu cầu tìm nạp và dọn sạch tất cả rác khi khởi chạy, không lý tưởng nhưng đây là làm việc cho tôi là tốt.
SybrSyn

0

Tôi gặp phải vấn đề tương tự và không thực sự tìm ra giải pháp cho vấn đề gốc. Nhưng bây giờ tôi "bảo vệ" chế độ xem sử dụng dữ liệu được tham chiếu như thế này:

var body: some View {
    if (clip.isFault) {
        return AnyView(EmptyView())
    } else {
        return AnyView(actualClipView)
    }
}

var actualClipView: some View {
    // …the actual view code accessing various fields in clip
}

Điều đó cũng cảm thấy hacky, nhưng bây giờ hoạt động tốt. Nó ít phức tạp hơn so với việc sử dụng thông báo để "trì hoãn" xóa, nhưng vẫn nhờ vào câu trả lời của sTOO cho gợi ý với .isFault!

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.