アプリ作成環境
- XCode Version 11.3
- SwiftUI
背景
SwiftUIのお勉強に、CoreDataを使った簡単なListViewーEditアプリを作ろうと思った。
完成イメージ
課題
編集画面で編集して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)
}
}
}