I have two Entities, namely
Clinic
Department
The relationship between them is many-to-many. i.e a clinic has many departments and a department has many clinics associated.
I have setup the entities in coredata.xcdatamodeld and generated the NSManagedObject classes. PFA the graph
I have the following code which takes care of inserting departments and clinics
import Foundation
import CoreData
import SwiftyJSON
import Kingfisher
class InstallManager {
let moc = NetworkManager.managedObjectContext
let data: JSON!
init(withData data: JSON) {
self.data = data
}
// Install Department
private func installDepartment() {
for(_,jsonData):(String, JSON) in self.data["deparment"]["list"] {
let department = NSEntityDescription.insertNewObject(forEntityName: "Department", into: moc)
department.setValue(jsonData["id"].int32, forKey: "id")
department.setValue(jsonData["name"].string, forKey: "name")
department.setValue(jsonData["icon"].string, forKey: "icon")
department.setValue(jsonData["slug"].string, forKey: "slug")
department.setValue(jsonData["revision"].int32, forKey: "revision")
do {
try moc.save()
} catch {
fatalError("\(error)")
}
}
}
// Install Clinic
private func installClinic() {
let departmentList = fetchDepartmentList()
for (_,jsonData):(String, JSON) in self.data["clinic"]["list"] {
let coverPictureAbsolute = "\(jsonData["cover_picture"].string!)"
let coverPictureRelative = (coverPictureAbsolute as NSString).lastPathComponent
let clinic = NSEntityDescription.insertNewObject(forEntityName: "Clinic", into: moc)
clinic.setValue(jsonData["id"].int32, forKey: "id")
clinic.setValue(jsonData["name"].string, forKey: "name")
clinic.setValue(jsonData["slogan"].string, forKey: "slogan")
clinic.setValue(jsonData["profile"].string, forKey: "profile")
clinic.setValue(jsonData["address"].string, forKey: "address")
clinic.setValue(jsonData["telephone"].string, forKey: "telephone")
clinic.setValue(jsonData["email"].string, forKey: "email")
clinic.setValue(jsonData["website"].string, forKey: "website")
clinic.setValue(coverPictureRelative, forKey: "cover_picture")
clinic.setValue(jsonData["latitude"].string, forKey: "latitude")
clinic.setValue(jsonData["longitude"].string, forKey: "longitude")
clinic.setValue(jsonData["slug"].string, forKey: "slug")
clinic.setValue(jsonData["revision"].int32, forKey: "revision")
let clinicEntity = clinic.value(forKeyPath: "departments") as! Clinic
for department in departmentList {
clinicEntity.addToDepartments(department)
print(department.name!)
}
do {
try moc.save()
} catch {
fatalError("\(error)")
}
}
}
private func fetchDepartmentList() -> [Department] {
var departmentList:[Department]!
let request: NSFetchRequest<Department> = Department.fetchRequest()
do {
departmentList = try moc.fetch(request)
} catch {
fatalError("Cannot fetch clinics list \(error)")
}
return departmentList
}
public func install() {
self.installDepartment()
self.installClinic()
}
}
My problem is while inserting relations between clinic and departments, I have the list of department ids which I want to set relation with the clinic. As you can see in the above code, I tried the following code
let departmentList = fetchDepartmentList()
let clinicEntity = clinic.value(forKeyPath: "departments") as! Clinic
for department in departmentList {
clinicEntity.addToDepartments(department)
print(department.name!)
}
But the line clinicEntity throws me the following error
Could not cast value of type '_NSFaultingMutableSet' to 'APP.Clinic'
What is the issue here? How can I set the relation?
UPDATE:
Here is the auto-generated classes from core-data (NSManagedObject SubClass)
// filename: Clinic.swift
import Foundation
import CoreData
#objc(Clinic)
public class Clinic: NSManagedObject {
}
// filename: Clinic+CoreDataProperties
import Foundation
import CoreData
extension Clinic {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Clinic> {
return NSFetchRequest<Clinic>(entityName: "Clinic");
}
#NSManaged public var address: String?
#NSManaged public var cover_picture: String?
#NSManaged public var email: String?
#NSManaged public var id: Int32
#NSManaged public var latitude: NSDecimalNumber?
#NSManaged public var longitude: NSDecimalNumber?
#NSManaged public var name: String?
#NSManaged public var profile: String?
#NSManaged public var revision: Int32
#NSManaged public var slogan: String?
#NSManaged public var slug: String?
#NSManaged public var telephone: String?
#NSManaged public var website: String?
#NSManaged public var departments: NSSet?
}
// MARK: Generated accessors for departments
extension Clinic {
#objc(addDepartmentsObject:)
#NSManaged public func addToDepartments(_ value: Department)
#objc(removeDepartmentsObject:)
#NSManaged public func removeFromDepartments(_ value: Department)
#objc(addDepartments:)
#NSManaged public func addToDepartments(_ values: NSSet)
#objc(removeDepartments:)
#NSManaged public func removeFromDepartments(_ values: NSSet)
}
For a to-many relationship you need to get the mutableSetValueForKey because the type is NSSet rather than Clinic.
let clinicEntity = clinic.mutableSetValue(forKey: "departments")
for department in departmentList {
clinicEntity.add(department)
print(department.name!)
}
or simpler
let clinicEntity = clinic.mutableSetValue(forKey: "departments")
clinicEntity.addObjects(from: departmentList)
To use the autogenerated method you can replace
let clinicEntity = clinic.value(forKeyPath: "departments") as! Clinic
for department in departmentList {
clinicEntity.addToDepartments(department)
print(department.name!)
}
with
clinic.addToDepartments(NSSet(array: departmentList))
PS: I'd recommend to use the more convenient dot notation, for example
department.id = jsonData["id"].int32
department.name = jsonData["name"].string
department.icon = jsonData["icon"].string
department.slug = jsonData["slug"].string
department.revision = jsonData["revision"].int32
Related
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)
}
}
}
I have a crash while trying to save CoreData managed object like this:
func saveRide(rideDetails details: Ride) {
if managedContext != nil {
let ride = RideDetails(context: managedContext!)
ride.rideId = details.rideId
ride.avgSpeed = Int64(details.avgSpeed ?? 0)
ride.blindSpotLeft = Int64(details.blindSpotLeft ?? 0)
ride.blindSpotRight = Int64(details.blindSpotRight ?? 0)
ride.distance = details.distance ?? 0.0
ride.distanceKeeping = Int64(details.distanceKeeping ?? 0)
ride.forwardCollision = Int64(details.forwardCollision ?? 0)
ride.maxAngle = details.maxAngle ?? 0.0
ride.maxSpeed = details.maxSpeed ?? 0.0
var locations :[LocationDetails] = []
for locationDetails in details.locations {
let location = LocationDetails(context: managedContext!)
location.lat = locationDetails.lat ?? 0.0
location.lon = locationDetails.lon ?? 0.0
location.ride = ride
locations.append(location)
}
if locations.count > 0 {
let locationsSet = NSOrderedSet(array: locations)
ride.addToLocations(locationsSet)
}
do {
try self.managedContext?.save()
}
catch let error {
self.delegate?.didFailuredToCompleteTheOperation?(message: error.localizedDescription)
#if DEBUG
print("Failed to save:\(error)")
#endif
}
}
The error message I get in the console is
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
The managed object is:
import Foundation
import CoreData
#objc(RideDetails)
public class RideDetails: NSManagedObject {
}
//
// RideDetails+CoreDataProperties.swift
//
//
// Created by Maria Davidenko on 13/04/2021.
//
//
import Foundation
import CoreData
extension RideDetails {
#nonobjc public class func fetchRequest() -> NSFetchRequest<RideDetails> {
return NSFetchRequest<RideDetails>(entityName: "RideDetails")
}
#NSManaged public var avgSpeed: Int64
#NSManaged public var blindSpotLeft: Int64
#NSManaged public var blindSpotRight: Int64
#NSManaged public var distance: Float
#NSManaged public var distanceKeeping: Int64
#NSManaged public var forwardCollision: Int64
#NSManaged public var maxAngle: Float
#NSManaged public var maxSpeed: Float
#NSManaged public var rideId: String?
#NSManaged public var timestamp: Date?
#NSManaged public var locations: NSOrderedSet?
}
// MARK: Generated accessors for locations
extension RideDetails {
#objc(insertObject:inLocationsAtIndex:)
#NSManaged public func insertIntoLocations(_ value: LocationDetails, at idx: Int)
#objc(removeObjectFromLocationsAtIndex:)
#NSManaged public func removeFromLocations(at idx: Int)
#objc(insertLocations:atIndexes:)
#NSManaged public func insertIntoLocations(_ values: [LocationDetails], at indexes: NSIndexSet)
#objc(removeLocationsAtIndexes:)
#NSManaged public func removeFromLocations(at indexes: NSIndexSet)
#objc(replaceObjectInLocationsAtIndex:withObject:)
#NSManaged public func replaceLocations(at idx: Int, with value: LocationDetails)
#objc(replaceLocationsAtIndexes:withLocations:)
#NSManaged public func replaceLocations(at indexes: NSIndexSet, with values: [LocationDetails])
#objc(addLocationsObject:)
#NSManaged public func addToLocations(_ value: LocationDetails)
#objc(removeLocationsObject:)
#NSManaged public func removeFromLocations(_ value: LocationDetails)
#objc(addLocations:)
#NSManaged public func addToLocations(_ values: NSOrderedSet)
#objc(removeLocations:)
#NSManaged public func removeFromLocations(_ values: NSOrderedSet)
}
import Foundation
import CoreData
#objc(LocationDetails)
public class LocationDetails: NSManagedObject {
}
import Foundation
import CoreData
extension LocationDetails {
#nonobjc public class func fetchRequest() -> NSFetchRequest<LocationDetails> {
return NSFetchRequest<LocationDetails>(entityName: "LocationDetails")
}
#NSManaged public var lat: Double
#NSManaged public var lon: Double
#NSManaged public var ride: RideDetails?
}
My guess is that relationship does it, but I never worked with a relationship in CoreData before (only in RDBMS) so I'm struggling to fix it. If anyone has idea how to fix it, please,help, this is bug production.
Thanks in advance,
Maria.
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)
}
}
}
}
Trying to use EnvironmentObject to populate a view with data from Core Data.
struct ContentView : View {
#EnvironmentObject var quoteStore: QuoteStore
var body: some View {
HStack {
Text(quote.text)
Spacer()
}
}
}
And here's the NSManagedObject:
extension Quote {
public class func fetchRequest() -> NSFetchRequest<Quote> {
return NSFetchRequest<Quote>(entityName: "Quote")
}
#NSManaged var text: String
#NSManaged var title: String
#NSManaged var author: String
static func create(author: String, text: String, title: String, in context: NSManagedObjectContext) {
let newQuote = Quote(context: context)
newQuote.text = text
newQuote.title = title
newQuote.author = author
}
I get the error message "Type of expression is ambiguous without more context"
I have code that gets auto generated when I select "Create NSManagedObject Subclass" from the Editor in the top menu.
There are two functions I'd like to try to use, but I don't seem to be able to access them. The first is addToListsRel and the other is removeFromListsRel.
Using dot notation, they don't seem to available, so how is it I can use these?
import Foundation
import CoreData
extension Ent_Catalog {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Ent_Catalog> {
return NSFetchRequest<Ent_Catalog>(entityName: "Ent_Catalog");
}
#NSManaged public var brand: String?
#NSManaged public var id: Int32
#NSManaged public var image: NSData?
#NSManaged public var name: String?
#NSManaged public var price: NSDecimalNumber?
#NSManaged public var qty: NSDecimalNumber?
#NSManaged public var size: String?
#NSManaged public var itemsRel: Ent_Items?
#NSManaged public var listsRel: NSSet?
#NSManaged public var ingredientsRel: NSSet?
#NSManaged public var sectionsRel: Ent_Sections?
}
// MARK: Generated accessors for listsRel
extension Ent_Catalog {
#objc(addListsRelObject:)
#NSManaged public func addToListsRel(_ value: Ent_Lists)
#objc(removeListsRelObject:)
#NSManaged public func removeFromListsRel(_ value: Ent_Lists)
#objc(addListsRel:)
#NSManaged public func addToListsRel(_ values: NSSet)
#objc(removeListsRel:)
#NSManaged public func removeFromListsRel(_ values: NSSet)
}
// MARK: Generated accessors for ingredientsRel
extension Ent_Catalog {
#objc(addIngredientsRelObject:)
#NSManaged public func addToIngredientsRel(_ value: Ent_Ingredients)
#objc(removeIngredientsRelObject:)
#NSManaged public func removeFromIngredientsRel(_ value: Ent_Ingredients)
#objc(addIngredientsRel:)
#NSManaged public func addToIngredientsRel(_ values: NSSet)
#objc(removeIngredientsRel:)
#NSManaged public func removeFromIngredientsRel(_ values: NSSet)
}
I thought the dot notation would look like Catalog.listsRel.addToListsRel. Turns out that its actually Catalog.addToListsRel
Hope this helps someone else.