Fetch some data from Core Data - swift

I'm saving some data - name and surname. But i don't know how to fetch some data.
Here is my code, but i don't know why it doesn't work - i don't know
class UserIN: NSManagedObject {
#NSManaged var name: String?
}
I create NSManagedObject Subclass
func fetching(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let usersFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
do {
let fetchedUsers = try context.fetch(usersFetch) as! [UserIN]
print(fetchedUsers)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
}
Please tell me the reason why it doesn't display fetched data.

The class of the NSManagedObject subclass must match the name of the entity (unless you changed it manually in Interface Builder)
class Users: NSManagedObject {
#NSManaged var name: String?
}
Not related to the issue but in Swift 3 use a fetch request with static type rather than the generic NSFetchRequestResult:
func fetching(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let usersFetch = NSFetchRequest<Users>(entityName: "Users")
do {
let fetchedUsers = try context.fetch(usersFetch)
print(fetchedUsers)
} catch {
fatalError("Failed to fetch employees: \(error)")
}
}

Related

save string over saved string in core data

In my swift code below the code saves an item in core data. The goal is to overwrite that item. I am getting a runtime error at
CoreDataHandler.changeName(user: fetchUser!\[indexNumber\], jessica: "jo")
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I don't know how to wrap in the index number. The goal is it to print judo then jo
import UIKit;import CoreData
class ViewController: UIViewController {
var fetchUser: [UserInfo]? = nil
var indexNumber : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
CoreDataHandler.saveObject2( name: "judo")
getText(textNo: indexNumber)
saveTheItem()
}
#objc func saveTheItem(){
CoreDataHandler.changeName(user: fetchUser![indexNumber], jessica: "jo")
}
func getText(textNo:Int) {
// first check the array bounds
let info = helpText.shareInstance.fetchText()
if info.count > textNo {
if let imageData = info[textNo].name
{
print(imageData)
} else {
// no data
print("data is empty Textss")
}
} else {
// image number is greater than array bounds
print("you are asking out of bounds")
}
}
}
class CoreDataHandler : NSManagedObject {
class func saveObject2( name: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "UserInfo", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
do{
try context.save()
return true
}
catch {
return false
}
}
private class func getContext() -> NSManagedObjectContext{
let appD = UIApplication.shared.delegate as! AppDelegate
return appD.persistentContainer.viewContext
}
class func changeName(user: UserInfo,jessica : String) -> Bool
{
let context = getContext()
user.name = jessica
print(jessica)
do{
try context.save()
return true
}
catch{
return false
}
}
}
class helpText: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpText()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveName(data: String) {
let imageInstance = UserInfo(context: context)
imageInstance.name = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func fetchText() -> [UserInfo] {
var fetchingImage = [UserInfo]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserInfo")
do {
fetchingImage = try context.fetch(fetchRequest) as! [UserInfo]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
No offense but your code is a mess.
And there is a big misunderstanding. Core Data records are unordered, there is no index. To update a record you have to fetch it by a known attribute, in your example by name, update it and save it back.
This is a simple method to do that. It searches for a record with the given name. If there is one, update the attribute with newName and save the record.
The code assumes that there is a NSManagedObject subclass UserInfo with implemented class method fetchRequest.
func changeName(_ name: String, to newName: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request : NSFetchRequest<UserInfo> = UserInfo.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
let records = try context.fetch(request)
guard let foundRecord = records.first else { return }
foundRecord.name = newName
try context.save()
} catch {
print(error)
}
}
Regarding your confusing code:
Create CoreDataHandler as singleton (and it must not be a subclass of NSManagedObject). Move the Core Data related code from AppDelegate and the methods to read and write in this class.

Any clean way for batch inserting coredata objects with relationships?

I have been observing high CPU times in background threads while inserting the coredata objects, and from analyser i could find that it's coming majorly because of some relationships i was creating one by one, and those could be in thousands.
So i thought if i could create them with batch insert. I can do that easily for objects using without relationships using NSBatchInsertRequest, but with relationships, I can't seem to find any clean way.Without relationships, i can easily create dictionaries and insert using the above request.
​
With relationships, i also tried using the object handler method of NSBatchInsertRequest, but even that is giving me an exception
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'run' between objects in different contexts
This is how i am trying to make sure that the trackpoint getting added is using the run object from the same context as the one in which its being created
func addTrackPoints(run: RunModel, objectId: NSManagedObjectID) async throws {
let locations:[CLLocation] = run.getLocations()
let count = run.getLocations().count
var index = 0
let batchInsert = NSBatchInsertRequest(entity: TrackPoint.entity()) { (managedObject: NSManagedObject) -> Bool in
guard index < count else { return true }
if let trackPoint = managedObject as? TrackPoint {
let data = locations[index]
guard let run = try? StorageService.shared.getBackgroundContext().object(with: objectId) as? Run else {
fatalError("failed to get run object")
}
trackPoint.run = run
}
index += 1
return false
}
try await StorageService.shared.batchInsert(entity: TrackPoint.entity(), batchInsertRequest: batchInsert, context: StorageService.shared.getBackgroundContext())
}
I also tried it without accessing the object from same context but instead tried directly using the Run object that i had created. It didn't crash, but it still didn't create the relationship.Also it forced me to remove the concurrencydebug run argument.
func addTrackPoints(run: RunModel, object: Run) async throws {
let locations = run.getLocations()
let count = run.getLocations().count
var index = 0
let batchInsert = NSBatchInsertRequest(entity: TrackPoint.entity()) { (managedObject: NSManagedObject) -> Bool in
guard index < count else { return true }
if let trackPoint = managedObject as? TrackPoint {
let data:CLLocation = locations[index]
trackPoint.run = object
}
index += 1
return false
}
try await StorageService.shared.batchInsert(entity: TrackPoint.entity(), batchInsertRequest: batchInsert, context: StorageService.shared.getBackgroundContext()) }
StorageService
public func batchInsert(entity: NSEntityDescription, batchInsertRequest: NSBatchInsertRequest, context: NSManagedObjectContext? = nil) async throws {
var taskContext:NSManagedObjectContext? = context
if(taskContext == nil) {
taskContext = StorageService.shared.newTaskContext()
// Add name and author to identify source of persistent history changes.
taskContext?.name = "importContext"
taskContext?.transactionAuthor = "import\(entity.name ?? "entity")"
}
/// - Tag: performAndWait
try await taskContext?.perform {
// Execute the batch insert.
do{
let fetchResult = try taskContext?.execute(batchInsertRequest)
if let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
} catch {
self.logger.error("Failed to execute batch insert request. \(error)")
}
throw SSError.batchInsertError
}
logger.info("Successfully inserted data for \(entity.name ?? "entity")")
}
Any help would be deeply appreciated :-)
How app works, I send request to server, get some results and want data to be saved in core data for further usage to send request to server only when needed. so next time I will query data from database.
Here is sample:
I always save data in background context, which is configured like this:
func getBgContext() -> NSManagedObjectContext {
let bgContext = self.persistenceController.container.newBackgroundContext()
bgContext.automaticallyMergesChangesFromParent = true
bgContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return bgContext
}
Next I construct my data models like this so decoder will handle entity creation and data parsing + insertion in dbContext:
public class SomeDataModel: NSManagedObject, Codable {
var entityName: String = "SomeDataModel"
enum CodingKeys: String, CodingKey {
case id = "id"
case someData = "someData"
}
public required convenience init(from decoder: Decoder) throws {
guard
let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "SomeDataModel", in: context)
else {
throw DecoderConfigurationError.missingManagedObjectContext
}
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int32.self, forKey: .id)
someData = try values.decodeIfPresent(String.self, forKey: .someData)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(someData, forKey: .someData)
}
func toExternalModel() -> SomeExternalUsableModel {
return SomeExternalUsableModel(id: id, someData: someData)
}
}
extension SomeDataModel {
#nonobjc public class func fetchRequest() -> NSFetchRequest<SomeDataModel> {
return NSFetchRequest<SomeDataModel>(entityName: "SomeDataModel")
}
#NSManaged public var someData: String?
#NSManaged public var id: Int32
}
extension SomeDataModel: Identifiable {
}
to pass dbcontext to decoder I do next:
extension CodingUserInfoKey {
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")!
}
dbContext - create background context somewhere in API helper class, and use this context for all the parsings below.
and next I do parsing with decoder when respond from server comes:
let model = try self.dbContext.performAndWait {
let jsonDecoder = JSONDecoder()
let jsonEncoder = JSONEncoder()
// pass context to decoder/encoder
jsonDecoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.dbContext
jsonEncoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.dbContext
// parse model, used generic for reuse for other models
let model = try jsonDecoder.decode(T.self, from: result.data)
// after this line - all the data is parsed from response from server, and saved to dbContext, and contained in model as well
if self.dbContext.hasChanges {
do {
try self.dbContext.save()
self.dbContext.refreshAllObjects() // refresh context objects to ELIMINATE all outdated db objects in memory (specially when you will have relations, they could remain in memory until updated)
} catch let error {
// process error
}
}
return model
}
// do with saved and ready to use data in models whatever needed:
return model
and extensions used for performAndWait
extension NSManagedObjectContext {
func performAndWait<T>(_ block: () throws -> T) throws -> T? {
var result: Result<T, Error>?
performAndWait {
result = Result { try block() }
}
return try result?.get()
}
func performAndWait<T>(_ block: () -> T) -> T? {
var result: T?
performAndWait {
result = block()
}
return result
}
}

Saving tableview cells

i've tried saving the data of my tableview using core data however i haven't been able to get it to work with the way i've setup my code.
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell
cell.taskText.text = tasks[indexPath.row].name
cell.taskPriority.image = tasks[indexPath.row].priority
return cell
where tasks is
var tasks = [Task]()
and it looks like this
class Task {
var name = ""
var priority = UIImage()
var priorityInt = Int()
convenience init(priority: UIImage, name: String, priorityInt: Int) {
self.init()
self.name = name
self.priority = priority
self.priorityInt = priorityInt
PriorityInt is used to change the image which determines the priority of the task.
Leja, "Save data from a table view in the CoreData" doesn't make sense but I will try answering by talking about 2 things.
Saving on CoreData
Create your Data Model with your Task model.
Then you will end up having your Task class inheriting from NSManagedObject like this:
import CoreData
import Foundation
class TaskMO: NSManagedObject {
#NSManaged var name: String?
#NSManaged var priority: Int?
}
You will use NSManagedObjectContext to save your entity with the following code:
guard let task = NSEntityDescription.insertNewObjectForEntityForName("Task", inManagedObjectContext: managedObjectContext) as? TaskMO else { return
do {
try managedObjectContext.save()
} catch {
fatalError("Failed to save: \(error)")
}
Show local data on TableView or CollectionView
let managedObjectContext = …
let tasksFetch = NSFetchRequest(entityName: "Task")
do {
let fetchedTasks = try managedObjectContext.executeFetchRequest(tasksFetch) as! [TaskMO]
} catch {
fatalError("Failed to fetch tasks: \(error)")
}
For a huge amount of data, you can even use NSFetchedResultsController
You can read more about, samples and explanation are well documented. apple documentation here.

Swift CoreData Save Attribute with Type of Custom Class

I'm new to using CoreData as the database to store the users' data in order to support offline app usage. I have set up the data model for my objects and some of the attributes have to be stored in the type of my other object classes. However, it seems to be saved successfully but it returns nil when I tried to fetch the results from the database. I also looked into the SQLite database file to see what happened to the field that stored into CoreData giving me nil as the results (and the returning type is found to be Optional(Any)), and found that the attribute of the data was stored in the type of BLOB.
The results stored in SQLite:
Originally, my app can only be used when network is available but I'm trying to turn it be supporting offline access. However, I have be sticking with the issues and do not know how to solve it, and even I'm not sure whether I was doing in the right way.
One of the example of my custom object classes StudentMarkList:
import Foundation
import UIKit
import CoreData
#objc(StudentMarkList)
class StudentMarkList: NSObject {
var id = Int() // Student ID in DB (Primary Key)
var paperId = String() // Paper ID which the student attended
var studentClass = String() // Student class
var totalMark = Int() // Total mark of the attended paper
var allMarked = Bool() // Flag whether student's paper is all marked
var markList = [MarkList]() // Array of marks for each question <- Custom object class of defining the structure of each question's mark
override init() {
super.init()
}
init(id:Int, paperId:String, studentClass:String, totalMark:Int, allMarked:Bool, markList:[MarkList]){
self.id = id
self.paperId = paperId
self.studentClass = studentClass
self.totalMark = totalMark
self.allMarked = allMarked
self.markList = markList
}
func save() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
let entity = NSEntityDescription.entity(forEntityName: "StudentMarkList", in: managedContext)!
let studentMarkList = NSManagedObject(entity: entity, insertInto: managedContext)
studentMarkList.setValue(self.id, forKey: "id")
studentMarkList.setValue(self.paperId, forKey: "paperId")
studentMarkList.setValue(self.studentClass, forKey: "studentClass")
studentMarkList.setValue(self.totalMark, forKey: "totalMark")
studentMarkList.setValue(self.allMarked, forKey: "allMarked")
studentMarkList.setValue(self.markList, forKey: "markList")
if managedContext.hasChanges {
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
}
Model of StudentMarkList:
The corresponding custom object class MarkList:
import Foundation
import UIKit
import CoreData
#objc(MarkList)
class MarkList: NSObject, NSCoding {
func encode(with aCoder: NSCoder) {
aCoder.encode(questionId, forKey: "questionId")
aCoder.encode(questionNo, forKey: "questionNo")
aCoder.encode(questionMark, forKey: "questionMark")
}
required init?(coder aDecoder: NSCoder) {
guard let questionId = aDecoder.decodeObject(forKey: "questionId") as? Int,
let questionNo = aDecoder.decodeObject(forKey: "questionNo") as? Int,
let questionMark = aDecoder.decodeObject(forKey: "questionMark") as? String else {
return nil
}
self.questionId = questionId
self.questionNo = questionNo
self.questionMark = questionMark
}
var questionId = Int()
var questionNo = Int()
var questionMark = String()
override init() {
super.init()
}
init(questionId: Int, questionNo: Int, questionMark: String) {
self.questionId = questionId
self.questionNo = questionNo
self.questionMark = questionMark
}
func save() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
let entity = NSEntityDescription.entity(forEntityName: "MarkList", in: managedContext)!
let markList = NSManagedObject(entity: entity, insertInto: managedContext)
markList.setValue(self.questionId, forKey: "questionId")
markList.setValue(self.questionNo, forKey: "questionNo")
markList.setValue(self.questionMark, forKey: "questionMark")
if managedContext.hasChanges {
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
}
Model of MarkList:
The function save() is used to save the instance to CoreData as the corresponding object class.
Hope someone can help me or clarify me the concepts of using CoreData. Thank you!
To achieve custom class as attribute of your CoreData entity. Each class should be treated as separate entity and data linking should be manage through relationships
You have to create new entity for marklist, StudentMarkList will be your parent entity.
Simply add relation of marklist to studentMarkList, Select your StudentMarkList entity and create relation as shown in image. Don't worry about inverse follow next step it will be clear.
Now select Marklist entity and add relationship like shown below
Please make sure to change class codegen to use manual

Fetching core data issue

I have an array with a SQLite with about 2000 records and all are listed on a tableview. When one of the records are selected, it goes to the "speciesDetailViewController" were it displays details of that item, including the common name of that species.
Currently, all displayed fields are not editable.
I am now adding the ability for the user to to change one of the fields, their common name and the ability to add notes per species.
The minor change is saved in CoreData as I have no experience with SQLite (hired someone).
I am fairly certain the data is being stored as I have print commands showing so.
My issue seems to be retrieving the data.
Note that as editing this field is optional, not every species will have a record in coreData, only the species that the user updated their common name.
class SpeciesDetailData: NSManagedObject
{
#NSManaged var speciesName: String
#NSManaged var commonName: String
#NSManaged var commonForeignName: String
#NSManaged var speciesNote: String
}
.
var speciesDetailData : SpeciesDetailData?
var speciesDataObject: [NSManagedObject] = []
var speciesNameToSave = String()
#IBAction func ckSaveCommonNameButton(_ sender: Any) {
speciesNameToSave = speciesLabel.text!
self.saveSpeciesName(speciesName: speciesNameToSave)
let commonNameToSave = ckCommonNameTextField.text
self.saveCommonName(commonName: commonNameToSave!)
}
func saveCommonName (commonName: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SpeciesDetailData", in: managedContext)!
let saveEntity = NSManagedObject(entity: entity, insertInto: managedContext)
saveEntity.setValue(commonName, forKey: "commonName")
saveSpeciesName(speciesName: speciesNameToSave)
do {
try managedContext.save()
speciesDataObject.append(saveEntity)
print(commonName)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func saveSpeciesName (speciesName: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SpeciesDetailData", in: managedContext)!
let saveEntity = NSManagedObject(entity: entity, insertInto: managedContext)
saveEntity.setValue(speciesName, forKey: "speciesName")
do {
try managedContext.save()
speciesDataObject.append(saveEntity)
print(speciesName)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
Here is the retrieving function
func retrieveCoreDataSpecies () {
let context = (UIApplication.shared.delegate
as! AppDelegate).persistentContainer.viewContext
let entity = NSEntityDescription.entity(
forEntityName: "SpeciesDetailData", in: context)
let request: NSFetchRequest<SpeciesDetailData> = SpeciesDetailData.fetchRequest()
request.entity = entity
let pred = NSPredicate(format: "speciesName = %#", specieDetail.specie)
request.predicate = pred
do {
let results = try context.fetch(request as!
NSFetchRequest<NSFetchRequestResult>)
if (results.count > 0) {
let match = results[0] as! NSManagedObject
if speciesDetailData?.commonName != nil {
ckCommonNameTextField.text = match.value(forKey: "commonName") as? String
} else {
}
if ckNotesTextView.text == "" || ckNotesTextView.text == nil {
} else {
ckNotesTextView.text = match.value(forKey: "speciesNote") as! String
}
}
} catch let error {
print("Count not fetch \(error), \(error.localizedDescription)")
}
}
When it gets to the
if speciesDetailData?.commonName != nil
it thinks the record is empty and skips over the needed lines.
Any help is appreciated
You are creating separate objects in the two save functions. In each case you are setting only one of the properties, so after you call saveSpeciesName you will have created an object with species name set, and after you call saveCommonName you will have created a different object with the common name set. You need to set both the species name and the common name on the same object.