I'm having issues trying to append a core data entity called products - I used the same code that I used to add companies, which works fine. The only difference in the app is that there is just one "static" tableview of companies I guess you could say, whereas the tableview for products is dynamically set depending on which company cell is tapped. But I'm not sure how that would cause the problem.
I've been debugging/checking values all over the app since yesterday and it seems like no matter what I try to alter, products.count in numberOfRowsInSection remains 0.
I call a function called handleSave() when the user hits a done button after entering new values in three text fields, just like with my other object companies:
func handleSave() {
guard let newProductUrl = NSURL(string: urlTextField.text!) else {
print("error getting text from product url field")
return
}
guard let newProductName = self.nameTextField.text else {
print("error getting text from product name field")
return
}
guard let newProductImage = self.logoTextField.text else {
print("error getting text from product logo field")
return
}
self.productController?.save(name: newProductName, url: newProductUrl as URL, image: newProductImage)
let cc = UINavigationController()
let companyController = CompanyController()
viewController = companyController
cc.viewControllers = [companyController]
present(cc, animated: true, completion: nil)
}
This then calls the save function to append products:
func save(name: String, url: URL, image: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "Product",
in: managedContext)!
let product = NSManagedObject(entity: entity,
insertInto: managedContext)
product.setValue(name, forKey: "name")
product.setValue(url, forKey: "url")
product.setValue(image, forKey: "image")
do {
try managedContext.save()
products.append(product)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
tableView.reloadData()
}
And the data is fetched in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let companyToDisplay = self.navigationItem.title!
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Product")
fetchRequest.predicate = NSPredicate(format:"company.name == %#",companyToDisplay)
do {
products = try managedContext.fetch(fetchRequest)
print(products)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
When this exact same process is used for adding companies, the new company then appears in the tableview. However now with products, that is not the case; the tableview remains empty. I'm not sure what the difference is and I'm not experienced enough with core data that I feel like I can figure out where the problem lies.
Huge thanks in advance to anyone that can figure this out!
After invoking handleSave() & before performing table.reloadData(), you need to perform db fetch operations done in viewWillAppear().
Segregate your fetchRequest logic out of viewWillAppear() into a function called (say) fetchUpdatedData().
Now, after try managedContext.save(), do a fetchUpdatedData(), update your product data arrays & then update the UI by table.reloadData()
Use a NSFetchedResultsController to keep your view in sync with your model. NSFetchedResultsController monitors core-data and sends a delegate message when there are updates - then you can update your tableview. Otherwise you have to manually do a refresh whenever core-data changes.
Related
I'm trying to fetch the specific instance of an item in a list but I can't recall the best way to do so. This is in the detail view of a master-detail list that when text changes end, it updates the item in the master list. However, I cant seem to get it to update the correct item, I know it lies in this part of my code:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Event")
do {
let test = try context.fetch(fetchRequest)
let objectUpdate = test[0] as! NSManagedObject
objectUpdate.setValue(noteText.text, forKey: "title")
do {
try context.save()
}
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
Specifically this part:
let test = try context.fetch(fetchRequest)
let objectUpdate = test[0] as! NSManagedObject
objectUpdate.setValue(noteText.text, forKey: "title")
EDIT:
I forgot to post the question! Basically with this current method, when I finish typing in the text field (this is wrapped in a textFieldDidEndEditing function) it saves, but at the lowest item in the master tableview. I want it to update the item that was selected.
Since I'm trying to grab the item from the tableview and update it, what is the best method for this? I'm assuming that I need to identify the correct item, but I'm not sure of the best method for this.
I ended up fixing it, I made a call for the coredata object as a variable, then passed it through with a segue. then all i needed to do was make selectedNote?.title = noteText.text
This is the full complete code:
var selectedEvent: Event? = nil
#IBAction func textFieldEndEdit(_ sender: Any) {
print("edit end")
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
do {
selectedEvent?.title = noteText.text
do {
try context.save()
}
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
My code is trying to delete a element from a set via button. When the user presses the button the set is deleted from that page but if it is reloaded the code is still there. So it is not permanently deleted. All I am doing is trying to delete a element from the core data set. I save the code in one class and iand display it through another. Link to git https://github.com/redrock34/core-Data-Delete/blob/master/photography%202.zip.
extension ViewController : datacollectionProfotocol {
func deleteData(indx: Int) {
block.reloadData()
let date = users[indx]
users.remove(at: indx)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
request.predicate = NSPredicate(format:"atBATS = %#", date as CVarArg)
let result = try? context.fetch(request)
let resultData = result as! [NSManagedObject]
for object in resultData {
context.delete(object)
}
do {
try context.save()
print(" saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}}
coreData returns empty data when there should not be any, even if you uninstall the application and reinstall it and make a request to Сore Data, the context.fetch returns the data
get all Data in Сore Data
func getMyLoadBook(){
words.removeAll()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest:NSFetchRequest<Favorite> = Favorite.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
do {
let result = try! context.fetch(fetchRequest)
print(result)
if result.isEmpty {
emptyBookMark()
return
} else {
tableView.isHidden = false
}
for data in result as [NSManagedObject] {
if let _ = data.value(forKey: "word"){
let initData = Words(word: (data.value(forKey: "word") as? [String]) ?? [""], wordDesc: (data.value(forKey: "wordDesc") as? [String]) ?? nil, translation: (data.value(forKey: "translation") as? [String]) ?? [""], translDesc: (data.value(forKey: "translDesc") as? [String]) ?? nil)
words.append(initData)
}
}
}
tableView.reloadData()
}
I have these functions, but they are not called when I get data from coreData
// creates a path and checks for the presence of an element
static func coreDataResult(data: [[String?]?]?, completion: #escaping (NSFetchRequest<NSFetchRequestResult>, Favorite?, NSManagedObjectContext) -> ()){
guard let w = data?.first, let word = w, let t = data?.last, let transl = t else { return }
DispatchQueue.main.async {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "Favorite", in: context) else { return }
guard let taskObject = NSManagedObject(entity: entity, insertInto: context) as? Favorite else { return }
let predicate = NSPredicate(format: "word == %#", word)
let predicate2 = NSPredicate(format: "translation == %#", transl)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Favorite")
let andPredicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, predicate2])
fetchRequest.predicate = andPredicate
completion(fetchRequest, taskObject, context)
}
}
// remove data from Сore Data
static func deleteFromCoreData(data: [[String?]?]?){
coreDataResult(data: data, completion: { (result, taskObject, context) in
do {
let fetchedEntities = try context.fetch(result) as! [Favorite]
if let entityToDelete = fetchedEntities.first {
context.delete(entityToDelete)
}
do {
try context.save()
if let data = getDataFromContext(result:fetchedEntities){
Analytics.logEvent("RemovedFavorite", parameters: ["word": data.0, "translation": data.1])
YMMYandexMetrica.reportEvent("RemovedFavorite", parameters: ["word": data.0, "translation": data.1], onFailure: nil)
}
} catch {
print(error)
}
} catch { print(error) }
})
}
// add data to Сore Data
static func saveWithModelToCoreData(_ words: Words){
DispatchQueue.main.async {
coreDataResult(data: [words.word, words.translation], completion: { (result, taskObject, context) in
do {
let fetchedEntities = try context.fetch(result) as! [Favorite]
if let _ = fetchedEntities.first?.word {
print("the element already have in coreData")
} else {
taskObject?.setValue(words.word, forKey: "word")
taskObject?.setValue(words.translation, forKey: "translation")
taskObject?.setValue(words.descript, forKey: "wordDesc")
taskObject?.setValue(words.translDesc, forKey: "translDesc")
do {
try context.save()
} catch {
print(error)
}
}
} catch {
print(error)
}
})
}
}
that's what result returns
[<Favorite: 0x283478500> (entity: Favorite; id: 0x281306ee0 <x-coredata:///Favorite/t722DD7F9-8DD7-4AC4-AA20-02324AB1B08713> ; data: {
translDesc = nil;
translation = nil;
word = nil;
wordDesc = nil;
})
It seems that you are you using a simple core-data setup, where all read and write are done on the main thread to the viewContext. This setup is fine for simple application where you don't expect to do a bulk import, or have a huge amount of entities. It should simplify a lot of multithread issues so I am a little confused why you have such a complex setup with callbacks and DispatchQueue.main.async when everything should just simply run on the main thread. (Perhaps you are planing for a future with a more complex setup?).
In any event, one of the consequences of this is that any changes to the viewContext will appear in your app for the lifetime of the app, even if you don't call save. This is because there is a single context - so even it is not saved, it has still been changed.
In the method coreDataResult you create an empty object, and then in saveWithModelToCoreData it is either set with values and the context saved or it is found to already exist and no further action is taken. If coreDataResult returned on a background context that would be fine. The empty object would disappear when the background context. The problem is that you are writing to the viewContext so the context does not go away, and the object sticks around.
If the application would quit right then, you wouldn't see it in the next launch. But if save is called any time after, then the empty object will also be saved.
I would suggest not creating objects unless you already know that you want them. I would refactor so that there is a single function that checks for duplicate, and then creates and set or does nothing. As it is I don't see the value of the two different methods.
I'm a Swift newbie, building a Core Data app. I've built and run a sample app from Ray Wenderlich's site which works as expected.
I've lifted and modified the following excerpt from Ray's app:
var people: [NSManagedObject] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
// 2
let fetchrequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
// 3
do {
people = try managedContext.fetch(fetchrequest)
print("\(people.count) fetched from Person")
}
catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
This works as expected. However, the following is what I've implemented in my app:
var players: [NSManagedObject] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Golfer")
do {
players = try managedContext.fetch(fetchRequest)
print("\(players.count) fetched from Golfer")
// Load any saved players, otherwise load sample data.
if players.count == 0 {
// Load the sample data.
// There are no players to load
os_log("Loading sample players", log: OSLog.default, type: .debug)
loadSamplePlayers()
}
}
catch let error as NSError {
print("Could not fetch Golfers. \(error), \(error.userInfo)")
}
}
The sample code always returns the full results of the Person table and the person array count is always equal to the number of rows in the table.
However, for my version, it always reports that the players array has a count of zero. Consequently, the loadSamplePlayers() method is called each time (which adds three sample records into the Golfer table).
Using an SQL Lite database inspector, I can see that the Golfer table continues to grow after each invocation of the app.
I'm stumped on why these two, near-identical, code samples should behave differently.
Any insights would be much appreciated.
Thanks!
EDIT:
Here's the loadSamplePlayers() method and printDatabaseStats() which is also referenced:
private func loadSamplePlayers() {
let sampleFirstNames: [String] = ["Casper", "Jocelyn", "Arthur"]
let sampleLastNames: [String] = ["Vitturio", "Brown", "Mullard"]
let sampleEmails: [String] = ["a#b.com", "fred#x.co.uk", "last#ohm.com"]
let sampleHandicaps: [Double] = [16.2, 6.4, 27.2]
let entity = NSEntityDescription.entity(forEntityName: "Golfer", in: managedObjectContext!)!
for i in 1...3 {
let player = NSManagedObject(entity: entity, insertInto: managedObjectContext)
player.setValue(sampleFirstNames[i-1], forKeyPath: "firstName")
player.setValue(sampleLastNames[i-1], forKey: "lastName")
player.setValue(sampleEmails[i-1], forKey: "emailAddress")
player.setValue(sampleHandicaps[i-1], forKey: "exactHandicap")
// player.setValue(sampleMugshots[i-1], forKey: "mugShot")
print("Prepare to save")
do {
try managedObjectContext?.save()
players.append(player)
}
catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
print("There are now \(printDatabaseStats()) in the Golfers table")
}
}
private func printDatabaseStats() -> Int {
var count = 0
managedObjectContext?.performAndWait {
if let golferCount = try? self.managedObjectContext!.count(for: NSFetchRequest(entityName: "Golfer")) {
print("\(golferCount) Payers")
count = golferCount
}
}
return count
}
This appears to work successfully as the print(...) methods produce the expected console messages and the Golfer table is populated and contains the rows as defined.
Not included in the original question was a class definition, thus:
var managedObjectContext: NSManagedObjectContext? = (UIApplication.shared.delegate as? AppDelegate)?.managedObjectContext
This is defined differently to:
let managedContext = appDelegate.persistentContainer.viewContext
yet I was using them as if they were the same. I normalised the two so they are identical and the problem has gone.
Hello I am new to CoreData and been exploring it. Here the situation is I am downloading data from API call and storing it in CoreData and then later fetching all the data from CoreData.
This task is done on viewDidLoad method. So every time I open my application it calls this method and data is being downloaded. At also view did i store all of my data in an NSManagedObject array for a particular key.
Now this flow does duplicate entries in my CoreData. So what I want to do is I want keep the flow as it is. But whenever the data is fetched from API I want to check whether the data is available in that array or not? If yes then don't insert it into CoreData Entity, or else enter the data. The reason behind this flow is whenever the API gets updated it only downloads that data and insert it into CoreData else it fetches from Local CoreData itself.
Here is my code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
var allImageData:[Any] = []
var image: [NSManagedObject] = []
let urlForData = URL(string: "https://jsonplaceholder.typicode.com/comments")
override func viewDidLoad() {
super.viewDidLoad()
indicator.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(40.0), height: CGFloat(40.0))
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubview(toFront: self.view)
UIApplication.shared.isNetworkActivityIndicatorVisible = false
indicator.startAnimating()
callToFetchJson()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func callToFetchJson() { //1st This Function will be called
let request = URLRequest(url: urlForData!)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if data == nil && error == nil {
print("no data")
}
else if data != nil && error != nil {
print("Error")
}
else {
DispatchQueue.main.sync {
self.decodingJson(data!)
}
}
}.resume()
}
func saveToDB(_ name: String) {//This function is called to save image name to CoreData and it is called from decodingJson function
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "ImageDetails",
in: managedContext)!
let imageX = NSManagedObject(entity: entity,
insertInto: managedContext)
imageX.setValue(name, forKey: "imageName")
do {
try managedContext.save()
image.append(imageX)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func decodingJson(_ data: Data ) { //If we get data from API then this function will be called
do {
indicator.stopAnimating()
let allImage = data
allImageData = try JSONSerialization.jsonObject(with: allImage, options: JSONSerialization.ReadingOptions.allowFragments) as! [Any]
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "ImageDetails",
in: managedContext)!
let imageX = NSManagedObject(entity: entity,
insertInto: managedContext)
for xImg in allImageData {
let aImg = xImg as! [String:AnyObject]
let aImgName = aImg["name"] as! String
// I want some condition to check image array contains aImgName or not forKey: imageName
//Here I want a check whether the data we got after decoding json is available in image array or not.
//If it is available then don't call saveToDB func. Otherwise call it.
// Following I have tried
imageX.setValue(aImgName, forKey: "imageName")
if !self.image.contains(imageX) {
saveToDB(aImgName)
print("Data inserted")
}
//Above code is failing to do its task. So is there any other way?
}
self.tableView.reloadData()
}
catch {
}
}
func tableView(_ tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return self.allImageData.count;
}
func tableView(_ tableView: UITableView!, didSelectRowAtIndexPath indexPath: IndexPath!) {
print("You selected item #:\(indexPath.item)")
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell") as! tableViewCell
let imageX = image[indexPath.row]
cell.titleLabel.text = imageX.value(forKey: "imageName") as? String
return cell
}
}
I know its an old question but for anyone who is looking for this If you want unique data what you can do is add a constraint like below
Then in your AppDelegate add add this
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "MarketPulse")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
return container
}()
we have added a merge policy so if there is data already present it will update if not it will insert