Why does #Published not work in NSManagedObject? - core-data

#Published publishes correctly when used in an ObservableObject. But does not seem to publish when used in an NSManagedObject (which conforms to ObservableObject).
In the following example when using Data1 the text below the picker is updated. But using Data2 it is not. Why is that?
import SwiftUI
import CoreData
class Data1: ObservableObject {
#Published var direction: Direction = .left
}
class Data2: NSManagedObject { // NSManagedObject conforms to ObservableObject
#Published var direction: Direction = .left
}
enum Direction {
case left
case right
}
#main
struct SwiftUITestApp: App {
var body: some Scene {
WindowGroup {
ContentView().environmentObject(Data2()) // Or Data1() as needed
}
}
}
struct ContentView: View {
#EnvironmentObject private var data: Data2
var body: some View {
VStack {
Picker("", selection: $data.direction) {
Text("Left").tag(Direction.left)
Text("Right").tag(Direction.right)
}.pickerStyle(.wheel)
switch data.direction {
case .left:
Text("Selected: Left")
case .right:
Text("Selected: Right")
}
}.padding()
}
}

The solution is to add a transient Core Data attribute. Transient attributes are published but not persisted.
Alternatively, you can publish changes like this:
class Data2: NSManagedObject {
#Published var direction: Direction = .left { // #Published is optional
willSet {
objectWillChange.send()
}
}
}

Related

Fetch request must have an entity using #FetchRequest in SwiftUI

I am trying to use #FetchRequest to fetch all records from the SQLite database through CoreData. I have setup CoreData stack as shown below:
class CoreDataManager {
private let persistentContainer: NSPersistentContainer
static let shared = CoreDataManager()
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
private init() {
persistentContainer = NSPersistentContainer(name: "FooModel")
persistentContainer.loadPersistentStores { description, error in
if let error = error {
print("Unable to initialize Core Data \(error)")
}
}
}
}
#main
struct FooAppApp: App {
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, CoreDataManager.shared.context)
}
}
}
When I try to access it is my ContentView as shown below:
struct ContentView: View {
#FetchRequest(entity: MyList.entity(), sortDescriptors: []) var myLists: FetchedResults<MyList>
var body: some View {
Text("Hello, world!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I get the following error:
A fetch request must have an entity.'
terminating with uncaught exception of type NSException
Am I missing something?
UPDATE: I removed the MyList.entity() part from the FetchRequest and now it works.
Remove
entity: MyList.entity(),
From the FetchRequest

CoreData: why not refresh UI when book.isPublic toggled

'#Environment(.managedObjectContext) var context' will monitor the changes of context. When I click the button to toggle isPublic, I think it will cause to UI refreshing, Button text will change, But not, WHY?
struct BookInfoView: View {
#Environment(\.managedObjectContext) var context
var isPublic: Bool { return book.isPublic }
let book: Book
var body: some View {
print("refresh"); return
Group {
Button(action: onPublicToggled) {
Text(isPublic ? "private" : "public")
}
}
}
func onPublicToggled() {
context.perform {
book.isPublic.toggle()
try? context.save()
}
}
}
You need to observe a Book changes via ObservedObject wrapper, like
struct BookInfoView: View {
#Environment(\.managedObjectContext) var context
#ObservedObject var book: Book
var body: some View {
print("refresh"); return
Group {
Button(action: onPublicToggled) {
Text(book.isPublic ? "private" : "public")
}
}
}
func onPublicToggled() {
context.perform {
book.isPublic.toggle()
try? context.save()
}
}
}
Note: #Environment(\.managedObjectContext) var context relates to context itself, not to your CoreData objects.

swiftUI core data model

I have created a swiftUI view and trying to display a core data entity but the preview fails and the app crashes during runtime. How to instantiate a Movie object and use it as a state member in swiftUI?
import SwiftUI
struct SwiftUIView: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var movie: Movie = Movie()
var body: some View {
Text(movie.title ?? "empty")
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
SwiftUIView().environment(\.managedObjectContext, moc)
}
}
You should create CoreData object with context, so here is possible way
struct SwiftUIView: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var movie: Movie?
var body: some View {
Text(movie.title ?? "empty")
.onAppear {
self.movie = Movie(context: viewContext) // << here !!
}
}
}

SwiftUI Core Data Delete related Data of entity

im struggling to delete a related entity Data in SwiftUI.
I got 2 entities related.
Bean (main entity)
Shot (related entity)
many to one --> For each bean will be a lot of shots
When I try to build and run I get the error that bean is unresolved.
Here is my code:
import SwiftUI
struct ShotList: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Bean.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Bean.name, ascending: true)]) var beans: FetchedResults<Bean>
static let taskDateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
var body: some View {
NavigationView {
List {
ForEach(beans, id: \.self) { bean in
Section(header: HStack {
Text(bean.wrappedName)
Text(bean.wrappedRoaster)}) {
ForEach(bean.shotArray, id: \.self) { shot in
HStack {
Text(shot.wrappedTaste)
Text(shot.wrappedTexture)
Text("\(shot.yield, specifier: "%.0f") g")
Text("\(shot.doseAmount, specifier: "%.0f") g")
Text("\(shot.bruRatio, specifier: "%.0f")")
Text("\(shot.time, specifier: "%.0f") s")
Spacer()
Text("\(shot.dateOfShot ?? Date(), formatter: ShotList.taskDateFormat)").font(.footnote)
}
}.onDelete(perform: self.deleteShot)
.font(.body)
}
}
}.navigationBarTitle("Espresso Shotlist")
}
}
func deleteShot(at offset: IndexSet) {
for offset in offset {
let shot = bean.shotArray[offset]
moc.delete(shot)
}
try? moc.save()
}
}
struct ShotList_Previews: PreviewProvider {
static var previews: some View {
ShotList()
}
}
bean is only accessible in the closure: ForEach(beans, id: .self) { bean in
It won't be available in the func deleteShot.

How can I change a data value with CoreData in SwiftUI?

Change the value of a data using CoreData and SwiftUI
I'm trying to modify a boolean value isFavorite of a data with a button (heart) on a cell in a list.
Modele data
import Foundation
import CoreData
public class VoitureItem: NSManagedObject, Identifiable {
#NSManaged public var marque: String?
#NSManaged public var modele: String?
#NSManaged public var annee: String?
#NSManaged public var energie: String?
#NSManaged public var kilometrage: String?
#NSManaged public var isFavorite: Bool
}
extension VoitureItem {
static func getAllVoitureItems() -> NSFetchRequest<VoitureItem> {
let request:NSFetchRequest<VoitureItem> = VoitureItem.fetchRequest() as! NSFetchRequest<VoitureItem>
let sortDescriptor = NSSortDescriptor(key: "isFavorite", ascending: false)
request.sortDescriptors = [sortDescriptor]
return request
}
}
I use in my ContentView a managedObjectCOntext and a FetchRequest to show all the data in a List and to create new data.
I would like to know how I can just click on the button change the value of isFavorite in my CoreData.
ContentView() (main view)
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: VoitureItem.getAllVoitureItems()) var voitureItems: FetchedResults<VoitureItem>
var body: some View {
NavigationView {
List {//show list of all the data}
}
}
Button on the cell
Button(action: {
//var fav: Bool = false
print("favorite")
//HERE - Change isFavorite in coreData
}) {
if isFavorite {
Image(systemName: "heart.fill")
.foregroundColor(Color.yellow)
} else {
Image(systemName: "heart")
.foregroundColor(Color.gray)
}
}
Something like this should work.
List(voitureItems) { item in
ItemCell(item: $item)
}
...
struct ItemCell: View {
#Binding var item: VoitureItem
var body: some View {
Button(action: {
item.isFavourite.toggle()
}) {
if item.isFavourite {
Image(systemName: "heart.fill")
.foregroundColor(Color.yellow)
} else {
Image(systemName: "heart")
.foregroundColor(Color.gray)
}
}
}
}

Resources