Saving into multiple entities - Core Data - Swift - ios

Hopefully this will make sense.
I am trying to store data into 2 different entities from one function but not having much luck with storing data into the second entity.
My 2 entities are called - Job & ShootKitChecklist.
I have created a core data manager, I have stripped this back to just show you the section I am using:
struct CoreDataManager {
static let shared = CoreDataManager()
let persistentContainer: NSPersistentContainer = {
// initialization of core data stack
let container = NSPersistentContainer(name: "TBAShootCoreData")
container.loadPersistentStores { (storeDescription, error) in
if let error = error {
fatalError("loading of store failed: \(error)")
}
}
return container
}()
func createSKCItem(item: String, used: Bool, visible: Bool, job: Job) -> (ShootKitChecklist?, Error?) {
let context = persistentContainer.viewContext
// create Shoot kit item
let SKC = NSEntityDescription.insertNewObject(forEntityName: "ShootKitChecklist", into: context) as! ShootKitChecklist
SKC.job = job
SKC.setValue(item, forKey: "item")
SKC.setValue(used, forKey: "used")
SKC.setValue(visible, forKey: "visible")
do {
try context.save()
return (SKC, nil)
} catch let error {
print ("Failed to add Shoot Kit Item:", error)
return (nil, error)
}
}
}
When I try to save the data, the Job entity (First Entity)writes to the context and I can fetch it in another class.
The data I am trying to save to the ShootKitChecklist is from an array so I put my setValues into a for look. However, it seems to ignore saving any data to the entity.
var SKCequipment = ["A","B","C","D","E"]
#IBAction private func HandleSave(sender : UIButton) {
let context = CoreDataManager.shared.persistentContainer.viewContext
let job = NSEntityDescription.insertNewObject(forEntityName: "Job", into: context)
let SKC = NSEntityDescription.insertNewObject(forEntityName: "ShootKitChecklist", into: context)
job.setValue(jobBrandTextField.text, forKey: "jobBrand")
job.setValue(jobNameTextField.text, forKey: "jobName")
job.setValue(directorTextField.text, forKey: "directorName")
job.setValue(agencyTextField.text, forKey: "agencyName")
job.setValue(prodCoTextField.text, forKey: "prodCoName")
for item in SKCequipment {
print(item)
SKC.setValue(item, forKey: "item")
SKC.setValue(true, forKey: "used")
SKC.setValue(true, forKey: "visible")
}
do {
try context.save()
dismiss(animated: true) {
self.delegate?.didAddJob(job: job as! Job)
}
} catch let saveError {
print("Failed to save company:", saveError)
}
}
To test to see if the items have been added to the core data I am fetching the items like this:
guard let SKCitem = job?.skc?.allObjects as? [ShootKitChecklist] else { return}
self.skcitems = SKCitem
print(skcitems)
Thank you in advance, huge help!

Okay I have fixed it by adding another function to my protocol and appending the Entity in the delegate(I realise that I didn't share this before (apologies). Here is my new protocol:
protocol NewJobControllerDelegate {
func didAddJob(job : Job)
func didAddSKC(SKC : ShootKitChecklist)
}
And then in my HandleSave action I changed my for loop to this:
for item in SKCequipment {
let tuple = CoreDataManager.shared.createSKCItem(item: item, used: true, visible: true, job: job as! Job)
if let error = tuple.1 {
print("Cannot save items", error)
} else {
self.delegate?.didAddSKC(SKC: tuple.0!)
}
And finally my new function from my delegate:
func didAddSKC(SKC: ShootKitChecklist) {
ShootKit.append(SKC)
}
Thank you for your help!

Related

Getting nil after delete an object in Coredata Swift

I tried to store some data using CoreData.
I created Entity Name Users and some attributes namely age, username, password.
Successfully loading, retrieving.
When I delete some object in that entity using context.delete(user), It will be deleted but show nil after I retrieving again.
Showing nil for deleted object
Note: I write the code for predicate which format is "Username!=nil".
Is this the right way? or how to overcome without nil in core data?
import UIKit
import CoreData
class CoreDataVC: UIViewController {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var context:NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addAction(_ sender: Any){
if let newUser = openDatabse(){
saveData(UserDBObj:newUser)
}
_ = fetchData()
}
#IBAction func deleteAction(_ sender: Any){
if let newUser = openDatabse(){
deleteData(UserObj: newUser)
}
_ = fetchData()
}
// MARK: Methods to Open, Store and Fetch data
func openDatabse() -> NSManagedObject?{
context = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "Users", in: context) else {
print("Invalid Entity")
return nil
}
let newUser = NSManagedObject(entity: entity, insertInto: context)
return newUser
}
func deleteData(UserObj: NSManagedObject){
let result = self.fetchData()
if let data = result.last{
context.delete(data)
}
do {
try context.save()
}
catch{
print("Error on saving context")
}
_ = fetchData()
}
func saveData(UserDBObj:NSManagedObject)
{
UserDBObj.setValue("RDC1", forKey: "username")
UserDBObj.setValue("12341", forKey: "password")
UserDBObj.setValue("22", forKey: "age")
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed")
}
_ = fetchData()
}
func fetchData() -> [Users]
{
var users : [Users] = []
print("Fetching Data..")
let request = Users.fetchRequest()
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
users = result
for data in result {//as! [NSManagedObject] {
print("Username: ",data.username ?? "nil",", Age : ",data.age ?? "nil")
}
} catch {
print("Fetching data Failed")
}
return users
}
}
Thanks in Advance.

how to validate login form into next page using Sqllite in iOS?

**this is to how to create a login validation form to move from login to next view controller **
**fetching the data from database**
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
**it stores the data**
let managedContext = appDelegate.persistentContainer.viewContext
//it fetches the data
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Details")
**validation code to check it but the condition fails**
do {
let result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
if ([emailid.text].count != 0 && [password.text].count != 0){
if (emailid.text == data.value(forKey: "emailId") as? String) && (password.text == data.value(forKey: "passWord") as? String){
let secondvc = storyboard?.instantiateViewController(withIdentifier: "loginVcID") as! loginVc
self.navigationController?.pushViewController(secondvc, animated: true)
}
in this condition it is not moving to next view controller
to check another condition
}
else {
self.label.text = "enter a valid data"
}
}
}
**when it fails it goes to catch to show that**
catch
{
print("Failed")
}
}
}
**this code is for registration to save into database**
**to create a database and store the value**
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let detailEntity = NSEntityDescription.entity(forEntityName: "Details", in: managedContext)!
** creation of database**
let detail = NSManagedObject(entity: detailEntity, insertInto: managedContext)
detail.setValue(username.text, forKeyPath: "userName")
detail.setValue(emailid.text, forKey: "emailId")
detail.setValue(password.text, forKey: "passWord")
detail.setValue(city.text, forKey: "city")
**saving the data**
do {
try managedContext.save()
}
** it display whatever in that method**
catch let error as NSError
{
its shows error when it fails
print("Could not save. (error), (error.userInfo)")
}
Go to you appDelegate, you will find a line // MARK: - Core Data stack & // MARK: - Core Data Saving support, remove the saveContext() function & also remove persistentContainer.. Then
Add this class to your project
final class PersistenceManager {
private init() {}
static let shared = PersistenceManager()
// MARK: - Core Data stack
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: "ProjectNAME")
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)")
}
})
return container
}()
lazy var context = persistentContainer.viewContext
// MARK: - Core Data Saving support
func save() {
if context.hasChanges {
do {
try context.save()
} catch {
// 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func fetch<T: NSManagedObject>(_ objectType: T.Type) -> [T] {
let entityName = String(describing: objectType)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
do {
let fetchedObjects = try context.fetch(fetchRequest) as? [T]
return fetchedObjects ?? [T]()
} catch {
return [T]()
}
}
func deleteAll<T: NSManagedObject>(_ objectType: T.Type) {
let entityName = String(describing: objectType)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try persistentContainer.persistentStoreCoordinator.execute(deleteRequest, with: context)
} catch {
print(error.localizedDescription)
}
}
func delete(_ object: NSManagedObject) {
context.delete(object)
save()
}
}
To fetch data
PersistenceManager.shared.fetch(User.self)
To delete data
PersistenceManager.shared.delete(user)
To create user
let newUser = Users(context: PersistenceManager.shared.context)
newUser.name = "Zero Cool"
newUser.password = "qwerty"
PersistenceManager.shared.save()

CoreData returns empty data

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.

Save json to CoreData as String and use the String to create array of objects

I am creating an app for a radio station and I want to store "show" objects into an array. I use a webserver to supply json data to populate the array, but I want to store this json data into CoreData as a string so that access to the array doesn't depend on internet connection. Therefore, I want to update the string in CoreData on app launch, but create an array based off of the string stored in CoreData not on the json data from the webserver.
Here's my function to download the json data from the webserver and store it into a string:
func downloadShows() {
let urlPath = "http://dogradioappdatabase.com/shows.php"
guard let url = URL(string: urlPath) else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
let jsonAsString = self.jsonToString(json: dataResponse)
DispatchQueue.main.async {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task2 = WebServer(context: context) // Link Task & Context
task2.showsArray = jsonAsString
print(jsonAsString)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}
}
task.resume()
}
func jsonToString(json: Data) -> String {
let convertedString: String
convertedString = String(data: json, encoding: String.Encoding.utf8)! // the data will be converted to the string
return convertedString
}
Here's the function to create the shows array from the fetched json from CoreData:
func createShowsArray () -> Array<ShowModel> {
var array: Array<ShowModel> = Array()
var tasks: [WebServer] = []
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(WebServer.fetchRequest())
}
catch {
print("Fetching Failed")
}
let arrayAsString: String = tasks[0].showsArray!
print(arrayAsString)
do {
let data1 = arrayAsString.data(using: .utf8)!
let decoder = JSONDecoder()
array = try decoder.decode([ShowModel].self, from:
data1)
} catch let parsingError {
print("Error", parsingError)
}
return array
}
However, this does not correctly load the data into an array. I printed the value I saved to CoreData in the downloadShows() function (jsonAsString) and got this as a response:
[{"Name":"Example Show 2","ID":"2","Description":"This ...
But when I fetched the string from CoreData in the createShowsArray() function (arrayAsString), it had added "DOG_Radio.ShowModel"
[DOG_Radio.ShowModel(Name: "Example Show 2", ID: "2", Description: "This ...
The JSON Decoder does not decode arrayAsString into an actual array. It throws this back:
Error dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 1." UserInfo={NSDebugDescription=Invalid value around character 1.})))
Sorry for the long question, I just don't know how to use CoreData to save json as String then convert that String into an array later
It's a bad practice to store json data or 'whole raw data' into CoreData. Instead Store the Show itself as a NSManagedObject.
You can do this by converting the JSON data to an Object (which it looks like you are already doing), then creating CoreData NSManagedObjects from them.
Realistically if you have no trouble converting the data from JSON there is no need to convert it to a string before saving to CoreData. You can simply store the Data as NSData, i.e. transformable or binary data and reconvert it later if your fetch to the server fails.
However, thats not that reliable and much harder to work with in the long run. The data could be corrupt and/or malformed.
In short, you need a Data Model and a JSON readable Data Structure you can Convert to your Data Model to for CoreData to manage. This will become important later when you want to allow the user to update, remove, save or filter individual Show's.
Codable will allow you to covert from JSON to a Struct with JSONDecoder().decode(_:from:).
ShowModelCodeable.swift
import Foundation
struct ShowModelCodeable: Codable {
var name: String?
var description: String?
var producer: String?
var thumb: String?
var live: String?
var banner: String?
var id: String?
enum CodingKeys: String, CodingKey {
case name = "Name"
case id = "ID"
case description = "Description"
case producer = "Producer"
case thumb = "Thumb"
case live = "Live"
case banner = "Banner"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
description = try values.decode(String.self, forKey: .description)
producer = try values.decode(String.self, forKey: .producer)
thumb = try values.decode(String.self, forKey: .thumb)
live = try values.decode(String.self, forKey: .live)
banner = try values.decode(String.self, forKey: .banner)
id = try values.decode(String.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
}
}
Next, We'll need a Core Data Stack and a CoreData Entity. Its very common to create a Core Data Stack as a Class Singleton that can be accessed anywhere in your app. I've included one with basic operations:
DatabaseController.Swift
import Foundation
import CoreData
class DatabaseController {
private init() {}
//Returns the current Persistent Container for CoreData
class func getContext () -> NSManagedObjectContext {
return DatabaseController.persistentContainer.viewContext
}
static var persistentContainer: NSPersistentContainer = {
//The container that holds both data model entities
let container = NSPersistentContainer(name: "StackOverflow")
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.
*/
//TODO: - Add Error Handling for Core Data
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
class func saveContext() {
let context = self.getContext()
if context.hasChanges {
do {
try context.save()
print("Data Saved to Context")
} catch {
// 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
/* Support for GRUD Operations */
// GET / Fetch / Requests
class func getAllShows() -> Array<ShowModel> {
let all = NSFetchRequest<ShowModel>(entityName: "ShowModel")
var allShows = [ShowModel]()
do {
let fetched = try DatabaseController.getContext().fetch(all)
allShows = fetched
} catch {
let nserror = error as NSError
//TODO: Handle Error
print(nserror.description)
}
return allShows
}
// Get Show by uuid
class func getShowWith(uuid: String) -> ShowModel? {
let requested = NSFetchRequest<ShowModel>(entityName: "ShowModel")
requested.predicate = NSPredicate(format: "uuid == %#", uuid)
do {
let fetched = try DatabaseController.getContext().fetch(requested)
//fetched is an array we need to convert it to a single object
if (fetched.count > 1) {
//TODO: handle duplicate records
} else {
return fetched.first //only use the first object..
}
} catch {
let nserror = error as NSError
//TODO: Handle error
print(nserror.description)
}
return nil
}
// REMOVE / Delete
class func deleteShow(with uuid: String) -> Bool {
let success: Bool = true
let requested = NSFetchRequest<ShowModel>(entityName: "ShowModel")
requested.predicate = NSPredicate(format: "uuid == %#", uuid)
do {
let fetched = try DatabaseController.getContext().fetch(requested)
for show in fetched {
DatabaseController.getContext().delete(show)
}
return success
} catch {
let nserror = error as NSError
//TODO: Handle Error
print(nserror.description)
}
return !success
}
}
// Delete ALL SHOWS From CoreData
class func deleteAllShows() {
do {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "ShowModel")
let deleteALL = NSBatchDeleteRequest(fetchRequest: deleteFetch)
try DatabaseController.getContext().execute(deleteALL)
DatabaseController.saveContext()
} catch {
print ("There is an error in deleting records")
}
}
Finally, we need a way to get the JSON data and convert it to our Objects, then Display it. Note that when the update button is pressed, it fires getDataFromServer(). The most important line here is
self.newShows = try JSONDecoder().decode([ShowModelCodeable].self, from: dataResponse)
The Shows are being pulled down from your Server, and converted to ShowModelCodeable Objects. Once newShows is set it will run the code in didSet, here you can delete all the Objects in the context, then run addNewShowsToCoreData(_:) to create new NSManagedObjects to be saved in the context.
I've created a basic view controller and programmatically added a tableView to manage the data. Here, Shows is your NSManagedObject array from CoreData, and newShows are new objects encoded from json that we got from the server request.
ViewController.swift
import Foundation
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Properties
var Shows:[ShowModel]?
var newShows:[ShowModelCodeable]? {
didSet {
// Remove all Previous Records
DatabaseController.deleteAllShows()
// Add the new spots to Core Data Context
self.addNewShowsToCoreData(self.newShows!)
// Save them to Core Data
DatabaseController.saveContext()
// Reload the tableView
self.reloadTableView()
}
}
// Views
var tableView: UITableView = {
let v = UITableView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
lazy var updateButton: UIButton = {
let b = UIButton()
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Update", for: .normal)
b.setTitleColor(.black, for: .normal)
b.isEnabled = true
b.addTarget(self, action: #selector(getDataFromServer), for: .touchUpInside)
return b
}()
override func viewWillAppear(_ animated: Bool) {
self.Shows = DatabaseController.getAllShows()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(ShowCell.self, forCellReuseIdentifier: ShowCell.identifier)
self.layoutSubViews()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//TableView -
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DatabaseController.getAllShows().count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// 100
return ShowCell.height()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: ShowCell.identifier) as! ShowCell
self.Shows = DatabaseController.getAllShows()
if Shows?.count != 0 {
if let name = Shows?[indexPath.row].name {
cell.nameLabel.text = name
}
if let descriptionInfo = Shows?[indexPath.row].info {
cell.descriptionLabel.text = descriptionInfo
}
} else {
print("No shows bros")
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Show the contents
print(Shows?[indexPath.row] ?? "No Data For this Row.")
}
func reloadTableView() {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func layoutSubViews() {
let guide = self.view.safeAreaLayoutGuide
let spacing: CGFloat = 8
self.view.addSubview(tableView)
self.view.addSubview(updateButton)
updateButton.topAnchor.constraint(equalTo: guide.topAnchor, constant: spacing).isActive = true
updateButton.leftAnchor.constraint(equalTo: guide.leftAnchor, constant: spacing * 4).isActive = true
updateButton.rightAnchor.constraint(equalTo: guide.rightAnchor, constant: spacing * -4).isActive = true
updateButton.heightAnchor.constraint(equalToConstant: 55.0).isActive = true
tableView.topAnchor.constraint(equalTo: updateButton.bottomAnchor, constant: spacing).isActive = true
tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: spacing).isActive = true
}
#objc func getDataFromServer() {
print("Updating...")
let urlPath = "http://dogradioappdatabase.com/shows.php"
guard let url = URL(string: urlPath) else {return}
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
guard let dataResponse = data, error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
do {
self.newShows = try JSONDecoder().decode([ShowModelCodeable].self, from: dataResponse)
} catch {
print(error)
}
}
task.resume()
}
func addNewShowsToCoreData(_ shows: [ShowModelCodeable]) {
for show in shows {
let entity = NSEntityDescription.entity(forEntityName: "ShowModel", in: DatabaseController.getContext())
let newShow = NSManagedObject(entity: entity!, insertInto: DatabaseController.getContext())
// Create a unique ID for the Show.
let uuid = UUID()
// Set the data to the entity
newShow.setValue(show.name, forKey: "name")
newShow.setValue(show.description, forKey: "info")
newShow.setValue(show.producer, forKey: "producer")
newShow.setValue(show.thumb, forKey: "thumb")
newShow.setValue(show.live, forKey: "live")
newShow.setValue(show.banner, forKey: "banner")
newShow.setValue(show.id, forKey: "id")
newShow.setValue(uuid.uuidString, forKey: "uuid")
}
}
}
Try this it's work for me
// Convert JSON to String
func jsonToString(json: AnyObject)->String{
do {
let data1 = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted) // first of all convert json to the data
let convertedString = String(data: data1, encoding: String.Encoding.utf8) // the data will be converted to the string
return convertedString! // <-- here is ur string
} catch let myJSONError {
print(myJSONError)
}
return ""
}
// Convert JSON String to Dict
func convertToDictionary(text: String) -> NSDictionary!{
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? NSDictionary
} catch {
print(error.localizedDescription)
}
}
return nil
}

Core Data Value returns Nil

Hi i have been working with core data to store and retrieve some values(String only) from the core data. Here is how i am storing the values.
The Function :
public func saveStringValue(forKey: String, value: String) -> Bool{
var saved = false
if self.entityName != nil && self.appDelegate != nil{
let context = appDelegate?.persistentContainer.viewContext
if context != nil{
let entity = NSEntityDescription.entity(forEntityName: self.entityName!, in: context!)
let entityHandle = NSManagedObject(entity: entity!, insertInto: context!)
entityHandle.setValue(value, forKey: forKey)
do{
try context?.save()
saved = true
}catch let error as NSError{
saved = false
print("Error : \(error)")
}
}
}
return saved
}
Here is how i call it
let historyManager = HistoryManager(entity: "SearchHistory")
let titleInserted = historyManager.saveStringValue(forKey: "title", value: book.title!)
if(titleInserted == true)
{
print("Title Inserted to Entity")
}
if let image = book.imageUrl{
let imageInserted = historyManager.saveStringValue(forKey: "image", value: image)
if imageInserted == true{
print("Image Url Inserted to Entity")
}
}
I can see in the console printed that
Title inserted into entity
ImageInserted Into entity
Here is the code to retrieve the value from core data store
public func fetchAll() -> [Book]{
var books = [Book]()
let context = self.appDelegate?.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: self.entityName!)
//let fetchRequest: NSFetchRequest<SearchHistory> = SearchHistory.fetchRequest()
do{
let fetchedBooks = try context?.fetch(fetchRequest)
for aBook in fetchedBooks!{
if let title = aBook.value(forKey: "title"){
let book = Book(title: title as! String)
if let im = aBook.value(forKey: "image"){
book.imageUrl = im as! String
print("ImageUrl : \(im) : ")
}
else{
print("No Value for key : image")
}
books.append(book)
}
}
}
catch let error as NSError{
print("Fetch Error: \(error.localizedDescription)")
}
print("Books : \(books.count)")
return books
}
But when i run the code to retrieve the book imageUrl it returns nil and prints
No value for key : image
It retrieves the title but not the imageUrl.
Can you help me through this problem or point me to the right direction. And please do post the reason why i was getting this problem and how to solve it. Thanks.
Your problem is that your saveStringValue creates a new NSManagedObject instance each time you call it.
The first time you call saveStringValue you will create a SearchHistory object that has a title but no image. The second time you call it you will create another SearchHistory object with an image value but no title.
In my opinion, your saveStringValue function is unnecessary. Assuming your code is based on a template that resulted from clicking "use Core Data" in Xcode, you will have a SearchHistory class available and you can use something like this:
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newHistory = SearchHistory(context: context)
newHistory.title = book.title
newHistory.image = book.imageUrl
appDelegate.saveContext()

Resources