Swift: Saving (Over writing) & Loading CoreData - ios

First off, let me explain my app and its flow. The app opens, and the user creates a profile (stores all the data through Core Data). After the user clicks on create, it sends them to a Console Screen (which displays parts of the information the user input, such as their name through a segue). Theres a tab that lets them EDIT their profile (name, weight, address, etc). When the user edits their info (to change their name, weight, etc), it should also update the info displayed on the Console Page.
I've gotten the data to save and load. The issue I'm having is when trying to edit the data from the Edit Profile screen... The user changes the text in a field, and clicks save. For some reason, the data is NOT saving...at least that's what I believe it to be the issue. When the "Save" button is pressed, the text fields go back to what the user originally input on the Create Profile screen, regardless of what text is input.
Code following...
Person.swift
// This struct would to get the data from the user
struct PInfo {
var firstName: String?
var lastName: String?
var cityState: String?
var streetAddress: String?
var gender: String?
var weight: NSNumber?
var phoneNumber: String?
var contactName: String?
var contactPhone: String?
}
func save(withPersonInfo p: PInfo, withContext context: NSManagedObjectContext) {
let entityDescription = NSEntityDescription.entity(forEntityName: "Person", in: context)
let managedObject = NSManagedObject(entity: entityDescription!, insertInto: context) as! Person
managedObject.firstName = p.firstName
managedObject.lastName = p.lastName
managedObject.cityState = p.cityState
managedObject.streetAddress = p.streetAddress
managedObject.gender = p.gender
managedObject.weight = p.weight as! Int16
managedObject.phoneNumber = p.phoneNumber
managedObject.contactName = p.contactName
managedObject.contactPhone = p.contactPhone
do {
try context.save()
print("Saved Successful")
}
catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func fetchSingleUser(withContext context:NSManagedObjectContext) -> PInfo {
let request: NSFetchRequest<Person> = Person.fetchRequest()
let coreData_items = try? context.fetch(request)
guard let items = coreData_items,
let firstItem = items.first
else {
fatalError("Error while querying") }
print("Loaded CoreData Items: \(firstItem)")
return PInfo(firstName: firstItem.firstName!, lastName:firstItem.lastName!, cityState: firstItem.cityState!, streetAddress: firstItem.streetAddress!, gender: firstItem.gender!, weight: firstItem.weight as NSNumber, phoneNumber: firstItem.phoneNumber!, contactName: firstItem.contactName, contactPhone: firstItem.contactPhone)
}
func userDataExists(withContext context: NSManagedObjectContext) -> Bool {
let request: NSFetchRequest<Person> = Person.fetchRequest()
let coreData_items = try? context.fetch(request)
guard let items = coreData_items,
let _ = items.first
else {
return false }
return true
}
EditProfileViewController.swift
#IBAction func saveButton(_ sender: UIButton) {
//Save to CoreData
saveUsersInfo()
alertPopup(title: "Saved!", message: "Your information has been updated!")
updateTextFields()
}
func updateTextFields() {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let userInformation = fetchSingleUser(withContext: managedContext)
//Set UI Text Fields with Users Data
firstNameField.text = userInformation.firstName!
lastNameField.text = userInformation.lastName!
weightInputField.text = String(describing: userInformation.weight!)
genderInputField.text = userInformation.gender!
phoneNumberField.text = userInformation.phoneNumber!
streetAddressField.text = userInformation.streetAddress!
cityStateInputField.text = userInformation.cityState!
contactNameField.text = userInformation.contactName
contactPhoneNumberField.text = userInformation.contactPhone
print("Updated User Info Text Fields")
}
func saveUsersInfo() {
//Save to CoreData
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let userInfo = PInfo(firstName: firstNameField.text!, lastName: lastNameField.text!, cityState: cityStateInputField.text!, streetAddress: streetAddressField.text!, gender: genderInputField.text!, weight: Int16(weightInputField.text!)! as NSNumber, phoneNumber: phoneNumberField.text!, contactName: contactNameField.text!, contactPhone: contactPhoneNumberField.text!)
save(withPersonInfo: userInfo, withContext: managedContext)
print("User Info Saved")
updateTextFields()
}
}
I BELIEVE it's an issue with saving (due to debugging), but I'm not familiar enough with CoreData to know exactly what the issue is.
Any help/info is greatly appreciated!

I suspect your data is being saved. But you are creating a new object each time, rather than updating the values of the existing object. Whenever you call your save method, this line:
let managedObject = NSManagedObject(entity: entityDescription!, insertInto: context) as! Person
creates a new Person object. And when you call the fetchSingleUser method, you fetch ALL the Person objects:
let coreData_items = try? context.fetch(request)
but then use only the first of those items:
let firstItem = items.first
It happens that the first item is the original Person object, with the original values: hence the textFields revert to those original values.
If your app should have only one Person object, change the save method to fetch the existing object, and update the property values of that instance, for example in your save method:
var managedObject : Person
let request: NSFetchRequest<Person> = Person.fetchRequest()
let coreData_items = try? context.fetch(request)
if let items = coreData_items {
if items.count > 0 {
managedObject = items.first
} else {
managedObject = NSManagedObject(entity: entityDescription!, insertInto: context) as! Person
}
managedObject.firstName = p.firstName
... etc
} else {
// coreData_items is nil, so some error handling here
}

Related

Saving array to Core Data

I've created two arrays (imgUrl and imgTitle). I want to save these array values in Core Data. I tried like below. However, it is not successful.
//Mark:- Method to save data in local data base(CoreData)
func saveDataInLocal(imageUrl: [String], imageTitle: [String]){
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let contactEntity = NSEntityDescription.entity(forEntityName: "Photos", in: context)
let newContact = NSManagedObject(entity: contactEntity!, insertInto: context)
for eachValue in imageTitle{
newContact.setValue(eachValue, forKey: "imgTitle")
}
for eachValue in imageUrl{
newContact.setValue(eachValue, forKey: "imgUrl")
}
do {
try context.save()
fetchData()
} catch {
print("Failed saving")
}
}
XcmodelID is shown in image.
In these two arrays one is image title and another one image URL.
Fetching I'm doing like below.
//Mark:- Method to fetch data from local database(CoreData)
func fetchData(){
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Photos")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
imgTitleNew.append(data.value(forKey: "imgTitle") as! String)
imgUrlNew.append(data.value(forKey: "imgUrl") as! String)
}
} catch {
print("Failed")
}
DispatchQueue.main.async {
self.myCollectionView.reloadData()
}
}
Can somebody suggest how to save the array in Core Data?
Array data displayed below.
var imgUrl = [String]() //contains urls in array
var imgTitle = [String]() //contains titles in array
A simple solution is to save both arrays joined with tab (or other unique) characters and use computed properties for the conversion
Assuming the Core Data properties are declared as
#NSManaged public var imageURL: String
#NSManaged public var imageTitle: String
Add these two computed properties
var imageURLArray : [String] {
get { return imageURL.components(separatedBy: "\t") }
set { imageURL = newValue.joined(separator: "\t") }
}
var imageTitleArray : [String] {
get { return imageTitle.components(separatedBy: "\t") }
set { imageTitle = newValue.joined(separator: "\t") }
}

How to check the entity attribute name match, and rewrite(update) data for another attributes?

I'm using Core Data entity with attributes, here is generated subclass code:
extension City {
#nonobjc public class func fetchRequest() -> NSFetchRequest<City> {
return NSFetchRequest<City>(entityName: "City")
}
#NSManaged public var name: String?
#NSManaged public var description: String?
#NSManaged public var temp: Double
#NSManaged public var temp_max: Double
#NSManaged public var temp_min: Double
}
I'm parsing JSON data and handle it through Weather model, and save the data into the database using this code (it's work well):
func saveWeatherData() {
let ad = UIApplication.shared.delegate as! AppDelegate
let context = ad.persistentContainer.viewContext
let city = City(context: context)
city.name = Weather.locationName!
city.description = Weather.details!
city.temp = Weather.temp
city.temp_min = Weather.tempMin
city.temp_max = Weather.tempMax
ad.saveContext()
}
The question is... how can I check the coincidence of city names (name attribute)? and if such a city already exists in the database, instead of creating a new record, overwrite (update) the values of current attributes (description, temp, temp_max, temp_min)?
Thanks.
You simply need to try and fetch an existing object. If the fetch succeeds, update its properties. If a match isn't found then create a new object. Something like:
func saveWeatherData() {
let ad = UIApplication.shared.delegate as! AppDelegate
let context = ad.persistentContainer.viewContext
let fetchRequest:NSFetchRequest<City> = City.fetchRequest()
fetchRequest.predicate = NSPredicate(format:"name = %#",Weather.locationName)
fetchRequest.fetchLimit = 1
do {
let result = try context.fetch(fetchRequest)
let city = result.first ?? City(context: context)
city.name = Weather.locationName!
city.description = Weather.details!
city.temp = Weather.temp
city.temp_min = Weather.tempMin
city.temp_max = Weather.tempMax
ad.saveContext()
}
catch {
print("Error fetching city: \(error.localizedDescription)"
}
}

Swift Change value of Coredata, not create new

I am new and dont understand to any objC, and there is no swift tut.
I am doing app for week planing, i have button, that add me to every day
-name
-codeName
-Task01
.
.
-Task10
but if i want to edit one specific day, with another button, i always delete rest of the days, resp. i create new entity with a lot of nils and only the one day is full of information. here is my ghetto solution but it's not working right, it's working a few times, then it said it has about 512 searchResults.count and start lagging and dont work...
Please help me, how to change value of specific Attributes and dont create new entity
Here is my code for edit tuesday( rest of the week is same as monday, but its long code, so here is only code with monday and tuesday):
func getAndPrintData () {
let fetchRequest: NSFetchRequest<Workout> = Workout.fetchRequest()
do {
let searchResults = try getContext().fetch(fetchRequest)
print ("num of results = \(searchResults.count)")
for works in searchResults as [NSManagedObject] {
if let moc = managedObjectContext {
let workout = Workout(context: moc)
workout.mondayNameCD = works.value(forKey: "mondayNameCD") as! String?
workout.mondayCodeNameCD = works.value(forKey: "mondayCodeNameCD") as! String?
workout.mondayTask01 = works.value(forKey: "mondayTaks01") as! String?
workout.mondayTask02 = works.value(forKey: "mondayTaks02") as! String?
workout.mondayTask03 = works.value(forKey: "mondayTaks03") as! String?
workout.mondayTask04 = works.value(forKey: "mondayTaks04") as! String?
workout.mondayTask05 = works.value(forKey: "mondayTaks05") as! String?
workout.mondayTask06 = works.value(forKey: "mondayTaks06") as! String?
workout.mondayTask07 = works.value(forKey: "mondayTaks07") as! String?
workout.mondayTask08 = works.value(forKey: "mondayTaks08") as! String?
workout.mondayTask09 = works.value(forKey: "mondayTaks09") as! String?
workout.mondayTask10 = works.value(forKey: "mondayTaks10") as! String?
workout.tuesdayNameCD = DayName.text!
workout.tuesdayCodeNameCD = CodeNameTextField.text!
workout.tuesdayFocusCD = IconImageTextField.text!
workout.tuesdayTask01 = Task01TextField.text!
workout.tuesdayTask02 = Task02TextField.text!
workout.tuesdayTask03 = Task03TextField.text!
workout.tuesdayTask04 = Task04TextField.text!
workout.tuesdayTask05 = Task05TextField.text!
workout.tuesdayTask06 = Task06TextField.text!
workout.tuesdayTask07 = Task07TextField.text!
workout.tuesdayTask08 = Task08TextField.text!
workout.tuesdayTask09 = Task09TextField.text!
workout.tuesdayTask10 = Task10TextField.text!
}
}
} catch {
print("Error with request: \(error)")
}
saveToCoreData() {
self.navigationController!.popToRootViewController(animated: true)
}
}
func saveToCoreData(completion: #escaping ()->Void){
managedObjectContext!.perform {
do {
try self.managedObjectContext?.save()
completion()
print("Product saved to CoreData")
} catch let error {
print("Could not save Product to CoreData: \(error.localizedDescription)")
}
}
}
var managedObjectContext: NSManagedObjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
Inside your for loop, you already have a managed object works. Just update the values on it instead of creating a new Workout.

Updating CoreData adds a lot of nil values

I am trying to implement custom class to handle core data operations. It works great when creating new values. However when I want to update values I get nil entries in core data. Here is my code so far
/**
Update all records in given entity that matches input records
- parameters:
- entityName: name of entity to fetch
- updateBasedOnKey: name of key which will be used to identify entries that are going to be udpated
- values: NSMutableArray of all elements that are going to be updated
- important: if object with given updateBasedOnKey doesnt exist it will be created
- returns: nothing
*/
func updateRecord(entity: String, updateBasedOnKey: String, values: NSMutableArray){
let entityDescription = NSEntityDescription.entityForName(
entity, inManagedObjectContext: self.managedObjectContext)
let results = getRecords(entity)
for(elements) in values{
var newEntry = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
//Determine whether to add new result or update existing
if(results.count > 0){
for result in results{
let entry = result as! NSManagedObject
if let keyValueToCompare = entry.valueForKey(updateBasedOnKey){
if (keyValueToCompare.isEqual(elements.valueForKey(updateBasedOnKey)) ){
//asign newEntry to result if found in entries
newEntry = entry
}
}
}
}
//update entry with new values
for(key, value) in elements as! NSMutableDictionary{
newEntry.setValue(value, forKey: key as! String)
}
//Try to save resulting entry
do {
try newEntry.managedObjectContext?.save()
} catch {
print(error)
}
}
}
/**
Fetch all records of given Entity in Core Data Model
- parameters:
- entityName: name of entity to fetch
- returns: NSArray of all records in given entity
*/
func getRecords(entity:String) -> NSArray{
let entityDescription = NSEntityDescription.entityForName(entity, inManagedObjectContext: self.managedObjectContext)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
var result = NSArray()
do {
result = try self.managedObjectContext.executeFetchRequest(fetchRequest)
} catch {
let fetchError = error as NSError
print(fetchError)
}
return result
}
I think that problem is somewhere in asigning newEntry a NSManagedObject.
Any ideas how to fix this and get rid of nils?
Thanks in advance
EDIT:
this is actual working code created by implementing Wain suggestion
func updateRecord(entity: String, updateBasedOnKey: String, values: NSMutableArray){
let entityDescription = NSEntityDescription.entityForName(
entity, inManagedObjectContext: self.managedObjectContext)
let results = getRecords(entity)
for(elements) in values{
//set to true if value was already found and updated
var newEntry : NSManagedObject?
//Determine whether to add new result or update existing
if(results.count > 0){
for result in results{
let entry = result as! NSManagedObject
if let keyValueToCompare = entry.valueForKey(updateBasedOnKey){
if (keyValueToCompare.isEqual(elements.valueForKey(updateBasedOnKey)) ){
//asign newEntry to result if found in entries
newEntry = entry
}
}
}
}
if newEntry == nil {
newEntry = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
}
for(key, value) in elements as! NSMutableDictionary{
newEntry!.setValue(value, forKey: key as! String)
}
}
}
You're right, the problem is that you're creating and inserting a new object each time. Instead you should be passing the object to update or running a fetch request to find it, then updating it.
It looks like your intention is to fetch, and the new entry should just be a reference, not initialised. So:
var newEntry : NSManagedObject?

Saving a Dictionary to Core Data

My app parses podcast RSS feeds. I use 2 entities: Podcasts (to hold podcast-related data) and Episodes (Episodes data like summaries etc). After parsing a feed, I store the list of episodes in an Array called "episodesToDisplay". When a user subscribes to a podcast, I want to save the data held by that array in Core Data. Here is my code which throws an error on the annotated line below:
class Podcasts: UITableViewController {
var currentPodcast: Podcasts!
override func viewDidLoad() {
super.viewDidLoad()
let podcastsEntity = NSEntityDescription.entityForName("Podcasts", inManagedObjectContext: self.managedContext)
let podcastsFetch = NSFetchRequest(entityName: "Podcasts")
var error: NSError?
let result = self.managedContext.executeFetchRequest(podcastsFetch, error: &error) as [Podcasts]?
if let resu = result {
println("res is \(resu.count)")
self.currentPodcast = resu[0] as Podcasts
} else {
println("did not work")
}
}
#IBAction func subscribe(sender: AnyObject) {
for dict: AnyObject in episodesToDisplay {
let episodesEntity = NSEntityDescription.entityForName("Episodes", inManagedObjectContext: self.managedContext)
let episodesToSave = Episodes(entity: episodesEntity!, insertIntoManagedObjectContext: self.managedContext)
var episodes = currentPodcast.episode.mutableCopy() as NSMutableOrderedSet
let btDict = dict as NSDictionary <---------------- Crash
episodesToSave.title = btDict["title"] as String
episodesToSave.summary = btDict["summary"] as String
episodesToSave.link = btDict["link"] as String
episodes.addObject(episodesToSave)
currentPodcast.episode = episodes.copy() as NSOrderedSet
}
// Save
var error:NSError?
if !self.managedContext.save(&error) {
println("could not save \(error)")
}
}
Any ideas please?
The error indicates that your array doesn't contain NSDictionary objects - that is why you get dynamic cast exception when you try and access an element as an NSDictionary.
From your comment it seems that your array actually contains MWFeedItem objects, so all you need to do is change your code to use that object type and then you can access the properties of the MWFeedItem -
#IBAction func subscribe(sender: AnyObject) {
for item: MWFeedItem in episodesToDisplay {
let episodesEntity = NSEntityDescription.entityForName("Episodes", inManagedObjectContext: self.managedContext)
let episodesToSave = Episodes(entity: episodesEntity!, insertIntoManagedObjectContext: self.managedContext)
var episodes = currentPodcast.episode.mutableCopy() as NSMutableOrderedSet
episodesToSave.title = item.title
episodesToSave.summary = item.summary
episodesToSave.link = item.link
episodes.addObject(episodesToSave)
currentPodcast.episode = episodes.copy() as NSOrderedSet
}

Resources