3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CoreDataのデータを更新した際にListViewが更新されない問題について

Last updated at Posted at 2019-12-14

アプリ作成環境

  • XCode Version 11.3
  • SwiftUI

背景

SwiftUIのお勉強に、CoreDataを使った簡単なListViewーEditアプリを作ろうと思った。

完成イメージ

  1. リストにある項目を選んで編集画面に遷移
  2. テキストフィールドに表示されている値を変更
  3. Saveボタンを押す
  4. リストビューに戻り、表示されている文字列が変更されている
    goal-image.gif

課題

編集画面で編集してSaveボタンを押してリストビューに戻った時に、文字列の変更が反映されなかった。  
(CoreData上では変更されているらしく、アプリを再起動すると反映される)

問題のあったコード

import SwiftUI
import CoreData
import Combine

public class Item: NSManagedObject, Identifiable {
    @NSManaged var uuid: UUID
    @NSManaged var name: String
}

struct ContentView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(entity: Item.entity(), sortDescriptors: []) var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(self.items) { item in
                    NavigationLink(destination: DetailView(item: item)){
                        ItemRowView(item: item)
                    }
                }
            }
        .navigationBarItems(trailing: AddItemButton())
        }
    }
}

struct ItemRowView: View {
    var item: Item
    var body: some View {
        VStack(alignment: .leading) {
            Text(item.name).font(.headline)
        }
    }
}

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

    var item: Item
    
    @State var name: String = ""
    
    var body: some View {
        VStack{
            Group {
                TextField("name", text: self.$name)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
            }.onAppear(perform: {
                self.name = self.item.name
            })
            Button(action: {
                self.item.name = self.name
                do {
                    try self.managedObjectContext.save()
                } catch {
                    print(error)
                }
                self.presentationMode.wrappedValue.dismiss()

            }, label: { Text("Save") })
        }
    }
}

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

    var body: some View {
        Button(action: {
            let item = Item(context: self.managedObjectContext)
            item.uuid = UUID()
            item.name = "New Item"
            do {
                try self.managedObjectContext.save()
            } catch {
                print(error)
            }

        }, label: {
            Image(systemName: "plus")
        })
    }
}

解決方法

その1

ContentView内のItemRowViewの部分を展開?する。
これで何故動くのか疑問があったがとりあえず動いた。
いろいろやっていく中で、itemのObserveができてないのでは?と予想(言葉の使い方がおかしいかもしれない)。

struct ContentView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(entity: Item.entity(), sortDescriptors: []) var items: FetchedResults<Item>

    var body: some View {
        NavigationView {
            List {
                ForEach(self.items) { item in
                    NavigationLink(destination: DetailView(item: item)){
                        //ItemRowView(item: item)
                        VStack(alignment: .leading) {
                            Text(item.name).font(.headline)
                        }
                    }
                }
            }
        .navigationBarItems(trailing: AddItemButton())
        }
    }
}
//struct ItemRowView: View {
//    var item: Item
//    var body: some View {
//        VStack(alignment: .leading) {
//            Text(item.name).font(.headline)
//        }
//    }
//}

その2

以下のQAを参考に修正した。こっちの方法のほうがそれっぽい気がする。
スマートなやり方があったら教えていただけると幸いです。
https://stackoverflow.com/questions/58068446/nsmanagedobject-changes-do-not-trigger-objectwillchange

public class Item: NSManagedObject, Identifiable {
    @NSManaged var uuid: UUID
    @NSManaged var name: String
    
    // willChangeValueをオーバーライド
    override public func willChangeValue(forKey key: String){
        super.willChangeValue(forKey: key)
        self.objectWillChange.send()
    }
}
・・・
struct ItemRowView: View {
    // 引数に@ObservedObjectを付与
    @ObservedObject var item: Item
    var body: some View {
        VStack(alignment: .leading) {
            Text(item.name).font(.headline)
        }
    }
}
3
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?