Saving array to Core Data - ios

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") }
}

Related

How to archive data in swift?

I am trying to archive data and want to store it in userdefault but app getting crash.
Also tried this
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: selectedPoductDetails, requiringSecureCoding: false)
selectedPoductDetails is dict of type [String: SelectedProductDetail]
import Foundation
class SelectedProductDetail {
let product: String
var amount: Double
var title: String
init(product: String, amount: Double, title: String ) {
self.product = product
self.amount = amount
self.title = title
}
}
May i know why its not working and possible solution for same?
For this case you can use UserDefaults
struct ProductDetail: Codable {
//...
}
let encoder = JSONEncoder()
let selectedProductDetails = ProductDetail()
// Set
if let data = try? encoder.encode(selectedProductDetails) {
UserDefaults.standard.set(data, forKey: "selectedProductDetails")
}
// Get
if let selectedProductDetailsData = UserDefaults.standard.object(forKey: "selectedProductDetails") as? Data {
let selectedProductDetails = try? JSONDecoder().decode(ProductDetail.self, from: selectedProductDetailsData)
}
As mentioned in the comments to use NSKeyedArchiver the class must adopt NSSecureCoding and implement the two required methods.
The types in your class are JSON compatible, so adopt Codable and archive the data with JSONEncoder (or PropertyListEncoder). You could even use a struct and delete the init method
struct SelectedProductDetail: Codable {
let product: String
var amount: Double
var title: String
}
var productDetails = [String: SelectedProductDetail]()
// populate the dictionary
do {
let data = try JSONEncoder().encode(productDetails)
UserDefaults.standard.set(data, forKey: "productDetails")
} catch {
print(error)
}
And load it
do {
guard let data = UserDefaults.standard.data(forKey: "productDetails") else { return }
productDetails = try JSONDecoder().decode([String: SelectedProductDetail].self, from: data)
} catch {
print(error)
}
Note:
UserDefaults is the wrong place for user data. It's better to save the data in the Documents folder

How to setup and fetched Strings Array in CoreData?

I have a method which need an Array of Strings for further calculations. For now I set up this array through UserDefaults, because it should store small (3-characters string) amount of data:
func setRow(for currency: Currency, in currencies: [Currency]) {
// initialise array from UserDefaults
var currencyRowsArray = UserDefaults.standard.stringArray(forKey: "currencyRowsArray") ?? [String]()
// calculations with the array (add or remove string object to\from it
if currency.isForConverter {
currencyRowsArray.append(currency.shortName!)
} else {
guard let row = currencyRowsArray.firstIndex(of: currency.shortName!) else { return }
currencyRowsArray.remove(at: row)
currency.rowForConverter = 0
}
for (row, object) in currencyRowsArray.enumerated() {
for currency in currencies {
if object == currency.shortName {
currency.rowForConverter = Int32(row)
}
}
}
// save array back to UserDefaults after calculations
UserDefaults.standard.set(currencyRowsArray, forKey: "currencyRowsArray")
}
But since I have a CoreData implemented also I decided to store it in CoreData rather than UserDefaults. Here is how I tried to implement that:
1.I have entity Currency, so I created an attribute currencyRowsArray: Type - Transformable, Custom Class - [String], Transformer - NSSecureUnarchiveFromData, Optional - YES (because I don't need it to initialize every time a created a Currency object with other attributes)
2.In the setRow method:
let request: NSFetchRequest<Currency> = Currency.fetchRequest()
request.predicate = NSPredicate(format: "currencyRowsArray IN %#", currencyRowsArray)
3.Initialize the empty array to fill it later with fetched Data:
currencyRowsArray = [String]()
4.Fetch the array:
do {
let fetchResult = try context.fetch(request)
currencyRowsArray = fetchedResult as! [String]
} catch {
print(error)
}
5.After calculations save the changes:
do {
try context.save()
} catch {
print(error)
}
Full changed code of the setRow method:
func setRow(for currency: Currency, in currencies: [Currency]) {
var currencyRowsArray = [String]()
let request: NSFetchRequest<Currency> = Currency.fetchRequest()
request.predicate = NSPredicate(format: "currencyRowsArray IN %#", currencyRowsArray)
do {
let fetchResult = try context.fetch(request)
currencyRowsArray = fetchResult as! [String]
} catch {
print(error)
}
if currency.isForConverter {
currencyRowsArray.append(currency.shortName!)
} else {
guard let row = currencyRowsArray.firstIndex(of: currency.shortName!) else { return }
currencyRowsArray.remove(at: row)
currency.rowForConverter = 0
}
for (row, object) in currencyRowsArray.enumerated() {
for currency in currencies {
if object == currency.shortName {
currency.rowForConverter = Int32(row)
}
}
}
do {
try context.save()
} catch {
print(error)
}
}
But every time the array loads as empty and when I make change, array doesn't add or remove items in it. What I did wrong?

Swift: Saving (Over writing) & Loading CoreData

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
}

How to access values of custom objects in an array?

I currently have a custom object
public struct GenreData : Decodable {
public let id : NSNumber?
public let name : String
public init?(json: JSON) {
guard let name : String = "name" <~~ json
else {return nil}
self.id = "id" <~~ json
self.name = name
}
}
I have an array of the custom object and am trying to access the 'id' part of the object so I can plug it in another function :
var genreDataArray: [GenreData] = []
var posterStringArray: [String] = []
var posterImageArray: [UIImage] = []
GenreData.updateAllData(urlExtension:"list", completionHandler: { results in
guard let results = results else {
print("There was an error retrieving info")
return
}
self.genreDataArray = results
for _ in self.genreDataArray {
if let movieGenreID = self.genreDataArray[0].id {//This is where the ID is needed to access the posters
print(movieGenreID)
//Update posters based on genreID
GenrePosters.updateGenrePoster(genreID: movieGenreID, urlExtension: "movies", completionHandler: {posters in
//Must iterate through multiple arrays with many containing the same poster strings
for poster in posters {
//Check to see if array already has the current poster string, if it does continue, if not append to array
if self.posterStringArray.contains(poster){
continue
} else {
self.posterStringArray.append(poster)
//Use the poster string to download the corresponding poster
self.networkManager.downloadImage(imageExtension: "\(poster)",
{ (imageData) //imageData = Image data downloaded from web
in
if let image = UIImage(data: imageData as Data){
self.posterImageArray.append(image)
}
})
}
}
})
}
}
DispatchQueue.main.async {
self.genresTableView.reloadData()
}
})
As of right now all its doing is accessing the first array of the JSON data(there are 19 arrays)and appending every string from that first array to the posterStringArray but instead I need it to go through each array and only append the string if doesn't already exist in the posterStringArray.
I am not 100 percent sure I understood your question, but shouldn't you replace:
for _ in self.genreDataArray {
if let movieGenreID = self.genreDataArray[0].id
with
for item in self.genreDataArray {
if let movieGenreID = item.id
update
to only add one item try adding a break after getting the image:
//Use the poster string to download the corresponding poster
self.networkManager.downloadImage(imageExtension: "\(poster)",
{ (imageData) //imageData = Image data downloaded from web
in
if let image = UIImage(data: imageData as Data){
self.posterImageArray.append(image)
}
})
break

Retrieve data from first only item in for loop

I have a query that gets objects from the server I'm then reducing the number of objects by matching "packName" to "className" which should just give me the children of "packName".
from this i am populating an array of struct items and pulling out the data for the first index of the array.
this is fine but I'm just a bit concerned that if the number of children increases this may slow processing down. so i was wondering if there was a way to just retrieve the first item of the for loop, which is all I'm after as the query has been sorted in ascending order.
this is the function code below.
class func createHistory(packName: String, completeBlock: ((Bool) -> Void)? = nil) {
struct initialDataStruct {
var packNameStruct : String
var packIdStruct : String
var partNameStruct : String
var partIdStruct : String
var partIndexStruct : Int
}
var initialDataArray = [initialDataStruct]()
let historyClass = PFObject(className: packName)
let query = PFQuery(className: "Part")
query.includeKey("fromPack")
query.order(byAscending: "partName")
query.fromLocalDatastore()
query.findObjectsInBackground { (objects, error) in
if error != nil {
print(error!)
}
else if let parts = objects {
for object in parts {
// if the fromPack column has data
if let fromPack = object.object(forKey: "fromPack") as? PFObject {
// create the class name from the pack name
if let className = (fromPack.object(forKey: "packName") as? String) {
// packName was sent from JVC
// this will limit array items to how ever many children packName has
if packName == className {
// because its sorted could probably just get the first item here
let packName = fromPack.object(forKey: "packName") as! String
let packId = fromPack.objectId as String!
let partName = object.object(forKey: "partName") as! String
let partId = object.objectId as String!
let partIndex = 0
initialDataArray.append(initialDataStruct(packNameStruct: packName,
packIdStruct: packId!,
partNameStruct: partName,
partIdStruct: partId!,
partIndexStruct: partIndex))
}
}
}
} // for
historyClass.add(initialDataArray[0].packNameStruct, forKey: "packName")
historyClass.add(initialDataArray[0].partIdStruct, forKey: "packId")
historyClass.add(initialDataArray[0].partNameStruct, forKey: "partName")
historyClass.add(initialDataArray[0].partIndexStruct, forKey: "partIndex")
print(historyClass)
PFObject.pinAll(inBackground: [historyClass])
}
} // query
}

Resources