コア データをビュー監視オブジェクト変数に渡すと、ナビゲーション リンク ビューはビューが消えた後でもオブジェクトへの参照を保持するため、SwiftUI でコア データを使用することは不可能であることがわかりました。そのため、コンテキストからオブジェクトを削除するとすぐに、エラー メッセージが表示されずにアプリがクラッシュします。
私は、コア データ オブジェクト変数をオプションとしてビュー モデルにラップし、コンテキスト削除アクションの直後にオブジェクトを nil に設定することでこれを確認しました。アプリは正常に動作しますが、コア データ オブジェクトを Swift UI ビューにバインドして真実のソースにする必要があるため、これは解決策ではありません。これはどのように動作するのでしょうか。どうやら、SwiftUI では、まったく複雑なものを作ることができないようです。
渡されたコア データ オブジェクトをオプションの @State に割り当てようとしましたが、うまくいきませんでした。フェッチされたオブジェクトなので、@Binding は使用できません。また、Swiftui コントロールにはバインディングが必要なので、変数も使用できません。@ObservedObject を使用するのが理にかなっています。ただし、これはオプションにできません。つまり、割り当てられたオブジェクトが削除されると、nil に設定できないため、アプリがクラッシュします。
以下は、デフォルトで監視可能なオブジェクトであるコア データ オブジェクトです。
class Entry: NSManagedObject, Identifiable {
@NSManaged public var date: Date
}
これは、コア データ入力オブジェクトを別のビューに渡すビューです。
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])
}
}
}
}
}
}
ここで、渡されたコア データ エントリ オブジェクトからすべての属性にアクセスするビューを示します。このエントリを任意のビューから削除すると、ここでまだ参照されているため、アプリがすぐにクラッシュします。これは、ナビゲーション リンクがアクセスされる前にすべての宛先ビューを初期化することにも関係していると思います。なぜそうするのか、意味がわかりません。これはバグですか、それともこれを実現するより良い方法がありますか?
onDisappear で削除を試みましたが、成功しませんでした。 JournalView から削除を実行しても、NavigationLink がオブジェクトを参照しているためクラッシュします。 興味深いことに、まだクリックされていない NavigationLink を削除するとクラッシュしません。
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")
}
}
}
}
アップデート
クラッシュにより、EntryView のエントリの最初の使用に移動し、スレッド 1: EXC_BAD_INSTRUCTION (コード = EXC_I386_INVOP、サブコード = 0x0) が読み取られます。これがスローされる唯一のメッセージです。
私が考えられる唯一の回避策は、コンテキストから削除しようとするのではなく、コア データ オブジェクト "isDeleted" にプロパティを追加して true に設定することです。その後、アプリを終了したとき、または起動したときに、isDeleted のすべてのエントリをクリーンアップして削除できますか? 理想的ではありません。ここでは何が間違っているのかを解明したいと思います。MasterDetailApp サンプルと何も違いがないように見えますが、これは機能しているようです。
ベストアンサー1
私も基本的に同じ問題を抱えていました。SwiftUI はすべてのビューを即座にロードするため、ビューには既存の CoreData オブジェクトのプロパティがロードされているようです。@ObservedObject 経由で一部のデータにアクセスするビュー内でこれを削除すると、クラッシュします。
私の回避策:
- 削除アクション - 延期されましたが、通知センター経由で終了しました
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")}
次のように Notification.name を定義する必要があります。
extension Notification.Name {
static var didSelectDeleteItem: Notification.Name {
return Notification.Name("Delete Item")
}
}
- 適切なビューで、削除メッセージを探します
// 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)}
}
- 一部の CoreData 要素にアクセスするビューでは安全を確保してください - isFault をチェックしてください。
VStack{
//Important: Only display text if the disease item is available!!!!
if !diseaseDetail.isFault {
Text (self.diseaseDetail.text)
} else { EmptyView() }
}
少しハッキーですが、私にとってはこれでうまくいきます。