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

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)
}
}
}
}

Related

CoreData FetchRequest is loading my List with Optional result data in addition to the FetchedData

I'm calling a #FetchRequest inside of the AdminView struct, where I wish to present the data inside of a List using a ForEach. I'm Requesting all User entity data but when I run the app, the list shows my 2 existing entry results but it also shows about 40 entries that default to the Optional String "TEST" for some reason.
I'm trying to understand how I can get the List to only show the two User Entries without showing the default String in the list.
Much appreciated!
Here is the relevant code:
import Foundation
import CoreData
extension User {
#nonobjc public class func fetchRequest() -> NSFetchRequest<User> {
return NSFetchRequest<User>(entityName: "User")
}
#NSManaged public var admin: Bool
#NSManaged public var company: String?
#NSManaged public var name: String?
#NSManaged public var password: String?
#NSManaged public var photo: Data?
#NSManaged public var username: String?
#NSManaged public var id: UUID?
#NSManaged public var tickets: NSSet?
}
// MARK: Generated accessors for tickets
extension User {
#objc(addTicketsObject:)
#NSManaged public func addToTickets(_ value: Ticket)
#objc(removeTicketsObject:)
#NSManaged public func removeFromTickets(_ value: Ticket)
#objc(addTickets:)
#NSManaged public func addToTickets(_ values: NSSet)
#objc(removeTickets:)
#NSManaged public func removeFromTickets(_ values: NSSet)
}
extension User : Identifiable {
}
extension User {
static func getAllUsers() -> NSFetchRequest<User> {
let request:NSFetchRequest<User> = User.fetchRequest()
let sortDescriptor = NSSortDescriptor(keyPath: \User.company, ascending: false)
request.sortDescriptors = [sortDescriptor]
return request
}
}
import SwiftUI
struct AdminView: View {
#Environment(\.managedObjectContext) var moc
#EnvironmentObject var goToContentView: moveToContentView
#ObservedObject var selectedUser : User
#State var selectedImageArray : [UIImage]
#FetchRequest(fetchRequest: User.getAllUsers()) var allUsers:FetchedResults<User>
var body: some View {
List{
ForEach(self.allUsers) { user in
VStack{
Text(user.company ?? "TEST")
}
}
}
.toolbar(content: {
EditButton()
})
.animation(.default)
.navigationBarBackButtonHidden(true)
.navigationTitle("ADMIN")
.navigationBarItems(leading: Button(action: {
if self.goToContentView.goToViewFromLogin == true {
self.goToContentView.goToViewFromLogin = false
} else {
self.goToContentView.goToViewFromRegister = false
}
}) {
HStack {
Text("Sign Out")
}
},trailing: HStack{
Image(systemName: "bell")
.font(.system(size: 30))
Image(uiImage: selectedImageArray.first!)
.resizable()
.scaledToFit()
.clipShape(Circle())
.frame(width: 50, height: 50)
Text(selectedUser.name!)
.font(.system(size: 20))
})
}
}
Add a filter to the list. You can also use let instead of the nil-coalescing operator. No need for the VStack since the Text will all be inside the list.
List {
ForEach(self.allUsers.filter({ $0.company != nil })) { user in
if let company = user.company {
Text(company)
}
}
}

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.

SwiftUI Picker in Form does not show checkmark from CoreData

I have an issue, after selecting a Value in a Picker, I do not see it as selected:
struct SampleView: View {
#FetchRequest(
entity: Category.entity(),
sortDescriptors: [
NSSortDescriptor(keyPath: \Category.title, ascending: true)
]
) var categories: FetchedResults<Category>
#State private var category = UUID()
var body: some View {
NavigationView {
VStack {
Form {
Picker(selection: $category, label: Text("Picker")) {
ForEach(categories) { cat in
Text(cat.title!)
}
}
}
}
}
}
}
My Core Data has this two properties:
#NSManaged public var title: String?
#NSManaged public var id: UUID?
Selection in Pickershould be the same type as presented values, so you need something like
#State private var category: UUID? = UUID()
Note: probably its due to not all code provided by in my example I needed to change your code as follows to make it compilable (tested with Xcode 11.2)
ForEach(categories, id: \.id) { cat in

Resources