How to setup and fetched Strings Array in CoreData? - ios
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?
Related
Firestore query multiple documents with for loop
I am trying to query multiple documents using a for-loop. My database set up looks like this: users -> wishlists -> all the users Wishlists(containing different Wishlists with name) -> wünsche The items are getting retrieved but in the wrong order. I tried couple of different things but nothing worked so far. func getWishes() { let db = Firestore.firestore() let userID = Auth.auth().currentUser!.uid var counter = 0 for list in self.dataSourceArray { print(list.name) // -> right order } for list in self.dataSourceArray { db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").getDocuments() { ( querySnapshot, error) in print(list.name) // -> wrong order if let error = error { print(error.localizedDescription) }else{ // create new Wish array var wList: [Wish] = [Wish]() for document in querySnapshot!.documents { let documentData = document.data() let wishName = documentData["name"] wList.append(Wish(withWishName: wishName as! String, checked: false)) } self.dataSourceArray[counter].wishData = wList counter += 1 } } } } I am calling this function inside another function that retrieves all the wishlist in the right order: func getWishlists() { let db = Firestore.firestore() let userID = Auth.auth().currentUser!.uid db.collection("users").document(userID).collection("wishlists").order(by: "listIDX").getDocuments() { ( querySnapshot, error) in if let error = error { print(error.localizedDescription) }else { // get all documents from "wishlists"-collection and save attributes for document in querySnapshot!.documents { let documentData = document.data() let listName = documentData["name"] let listImageIDX = documentData["imageIDX"] // if-case for Main Wishlist if listImageIDX as? Int == nil { self.dataSourceArray.append(Wishlist(name: listName as! String, image: UIImage(named: "iconRoundedImage")!, wishData: [Wish]())) // set the drop down menu's options self.dropDownButton.dropView.dropDownOptions.append(listName as! String) self.dropDownButton.dropView.dropDownListImages.append(UIImage(named: "iconRoundedImage")!) }else { self.dataSourceArray.append(Wishlist(name: listName as! String, image: self.images[listImageIDX as! Int], wishData: [Wish]())) self.dropDownButton.dropView.dropDownOptions.append(listName as! String) self.dropDownButton.dropView.dropDownListImages.append(self.images[listImageIDX as! Int]) } // reload collectionView and tableView self.theCollectionView.reloadData() self.dropDownButton.dropView.tableView.reloadData() } } self.theCollectionView.isHidden = false self.getWishes() } } *DataSourceArray in the right order: * Main Wishlist, Goals, boost Output from 2nd print-test: boost, Goals, Main Wishlist
Seems as though you are trying to make a bunch of API calls at once and it is returning values at different times. You could attempt to make your calls synchronously to maintain order or you could try to use dispatch groups like the pseudo code below: let myGroup = DispatchGroup() struct DataItem { let order: Int let data: DataYouWantToSave } var fetchedData = [DataItem]() for i in list { myGroup.enter() let dataItem = DataItem() dataItem.order = i db.collection... print("Finished request \(i)") dataItem.data = DataYouWantToSave fetchedData.apped(dataItem) myGroup.leave() } } myGroup.notify(queue: .main) { print("Finished all requests.") // Reorder your array of data items here. let sortedArray = fetchedData.sorted(by: { $0.order > $1.order }) // If you just want the array of data values let newData: [DataYouWantToSave] = sortedArray.map { $0.data } }
Swift generic functions issues. Decrease duplicating code
I want to understand how can decrease amount of duplicated code. I have two almost the same functions. The differences are next: firs function returns array of [ExerciseEntity] and second function returns array of [WorkoutEntity] func replaceExercisesIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [ExerciseEntity] { for jsonObject in jsonObjects { if let mobileLocalId = jsonObject["mobileLocalId"] as? String { if mobileLocalId.contains("<x-coredata://") { if let managedObject = try? transaction.fetchOne(From<ExerciseEntity>() .where( format: "%K == %#", #keyPath(BaseMO.id), mobileLocalId) ) { let editObject = transaction.edit(managedObject) if let identifier = jsonObject["id"] as? String { editObject?.id = identifier } } } } } let managedObjects = try! transaction.importUniqueObjects( Into<ExerciseEntity>(), sourceArray: jsonObjects) return managedObjects } func replaceWorkoutsIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [WorkoutEntity] { for jsonObject in jsonObjects { if let mobileLocalId = jsonObject["mobileLocalId"] as? String { if mobileLocalId.contains("<x-coredata://") { if let managedObject = try? transaction.fetchOne(From<WorkoutEntity>() .where( format: "%K == %#", #keyPath(BaseMO.id), mobileLocalId) ) { let editObject = transaction.edit(managedObject) if let identifier = jsonObject["id"] as? String { editObject?.id = identifier } } } } } let managedObjects = try! transaction.importUniqueObjects( Into<WorkoutEntity>(), sourceArray: jsonObjects) return managedObjects } This is a similar question related to how to use generic function I asked before. I implemented this in my code but: func importArray<T: ImportableUniqueObject>(from exercisesDict: [[String: Any]], transaction: BaseDataTransaction) -> [T] where T.ImportSource == [String: Any] { let managedObjects = try? transaction.importUniqueObjects(Into<T>(), sourceArray: jsonObjects) } But here is few things, with T type First - I can't add this code: editObject?.id = identifier as there is no id in T type. Second when I debug these generic functions debugger every time crashes: Message from debugger: The LLDB RPC server has crashed. The crash log is located in ~/Library/Logs/DiagnosticReports and has a prefix 'lldb-rpc-server'. Please file a bug and attach the most recent crash log. If interesting here is a file with log. I have not submitted it yet. For sure I can add a lot of prints to track behavior, though it's a but annoying) But main task is to get rid of duplication.
Try this (I have not tested): protocol MyProtocol { var id: Int { get set } } struct ExerciseEntity { var id: Int } struct WorkoutEntity { var id: Int } func replaceWorkoutsIdentifiers<T: MyProtocol>(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [T] { for jsonObject in jsonObjects { if let mobileLocalId = jsonObject["mobileLocalId"] as? String { if mobileLocalId.contains("<x-coredata://") { if let managedObject = try? transaction.fetchOne(From<T>() .where( format: "%K == %#", #keyPath(BaseMO.id), mobileLocalId) ) { let editObject = transaction.edit(managedObject) if let identifier = jsonObject["id"] as? String { editObject?.id = identifier } } } } } let managedObjects = try! transaction.importUniqueObjects( Into<T>(), sourceArray: jsonObjects) return managedObjects as! T } Using: let array: [ExerciseEntity] = replaceWorkoutsIdentifiers(from jsonObjects: ..., transaction: ...)
How to fetch contacts and store in array on iOS?
I am working on a Tinder Swiping application for iOS Contacts. I have imported the contacts library and successfully have gotten the contacts to print in the console. However, I am trying to dynamically add those names to a card like Tinder. I have created a model class to hold the name, however, I am unable to append my data to that model. struct ContactInfo { var name: String } let contactInfo = [ContactInfo]() func fetchContacts(){ let contactStore = CNContactStore() var contacts = [CNContact]() let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] let request = CNContactFetchRequest(keysToFetch: keys) do { try contactStore.enumerateContacts(with: request) { (contact, stop) in contacts.append(contact) var info = contact.givenName + " " + contact.familyName print(info) } } catch { print(error.localizedDescription) } } var userModels : [ContactInfo] = { var model : [ContactInfo] = [] for n in 1...10 { model.append(ContactInfo(name: names[Int(arc4random_uniform(UInt32(names.count)))])) } return model }() I would like all of my contacts to append to the model variable which is then returned to the cards.
As my understanding, the enumerateContacts is asynchronous. It means that, when your app is executing the line to create the userModels array, the contacts aren't fetched yet : so your array stays empty. I would try to move the userModels array creation in another controller, where you are displaying your cards. To achieve that, you can use a delegate, that receives the fetched contacts as a parameter. Then, you can assign your array with this parameter content and create your cards' content. Here is a great tutorial on how to use a delegate with Swift. Hope this will help you.
I have created helper class for that. That might help you. Example ContactSyncHelper.sharedInstance.getAllContacts { (contacts, error) in if error { print("error") }else { print(contacts) } } ContactSyncHelper import UIKit import Foundation import Contacts let kphone_number = "phone_number" let kcountry_code = "country_code" class ContactSyncHelper: NSObject { static let sharedInstance: ContactSyncHelper = { let instance = ContactSyncHelper() // setup code return instance }() // MARK: - Initialization Method override init() { super.init() } //Example // ContactSyncHelper.sharedInstance.getAllContacts { (contacts, error) in // if error { // print("error") // }else { // print(contacts) // } // } func getAllContacts(completion: ([NSMutableDictionary],Bool) -> ()) { switch CNContactStore.authorizationStatus(for: .contacts) { // Update our UI if the user has granted access to their Contacts case .authorized: break // Prompt the user for access to Contacts if there is no definitive answer case .notDetermined : completion([],true) break // Display a message if the user has denied or restricted access to Contacts case .denied, .restricted: //CommData.showAlert(self, withMsg: "Permission was not granted for Contacts.", withTitle: "Privacy Warning!", action: nil) completion([],true) break } let contactStore = CNContactStore() let keysToFetch = [ CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataAvailableKey, CNContactThumbnailImageDataKey] as [Any] // Get all the containers var allContainers: [CNContainer] = [] do { allContainers = try contactStore.containers(matching: nil) } catch { print("Error fetching containers") } var arrayNumbers: [NSMutableDictionary] = [] for container in allContainers { let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier) do { let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor]) containerResults.forEach { (contact:CNContact) in contact.phoneNumbers.forEach { (justPhone:CNLabeledValue) in let numberValue = justPhone.value let countryCode = numberValue.value(forKey: "countryCode") as? String var strphone = numberValue.stringValue strphone = strphone.replacingOccurrences(of: "(", with: "") strphone = strphone.replacingOccurrences(of: ")", with: "") strphone = strphone.replacingOccurrences(of: "-", with: "") strphone = strphone.replacingOccurrences(of: "+", with: "") strphone = strphone.components(separatedBy: .whitespaces).joined() if strphone.hasPrefix("0"){ strphone.remove(at: (strphone.startIndex)) } if(countryCode != nil) { var countryCode1:String = self.getCountryPhonceCode(country: countryCode!) if strphone.hasPrefix(countryCode1) { strphone = strphone.deletingPrefix(countryCode1) } countryCode1 = "+\(countryCode1)" let dict = NSMutableDictionary() dict.setValue(strphone, forKey: kphone_number) dict.setValue(countryCode1, forKey: kcountry_code) arrayNumbers.append(dict) } } } } catch { print("Error fetching results for container") } } completion(arrayNumbers,false) } func getCountryPhonceCode (country : String) -> String { if country.count == 2 { let x : [String] = ["972","IL","93","AF","355","AL","213","DZ","1","AS","376","AD","244","AO","1","AI","1","AG","54","AR","374","AM","297","AW","61","AU","43","AT","994","AZ","1","BS","973","BH","880","BD","1","BB","375","BY","32","BE","501","BZ","229","BJ","1","BM","975","BT","387","BA","267","BW","55","BR","246","IO","359","BG","226","BF","257","BI","855","KH","237","CM","1","CA","238","CV","345","KY","236","CF","235","TD","56","CL","86","CN","61","CX","57","CO","269","KM","242","CG","682","CK","506","CR","385","HR","53","CU" ,"537","CY","420","CZ","45","DK" ,"253","DJ","1","DM","1","DO","593","EC","20","EG" ,"503","SV","240","GQ","291","ER","372","EE","251","ET","298","FO","679","FJ","358","FI","33","FR","594","GF","689","PF","241","GA","220","GM","995","GE","49","DE","233","GH","350","GI","30","GR","299","GL","1","GD","590","GP","1","GU","502","GT","224","GN","245","GW","595","GY","509","HT","504","HN","36","HU","354","IS","91","IN","62","ID","964","IQ","353","IE","972","IL","39","IT","1","JM","81","JP","962","JO","77","KZ","254","KE","686","KI","965","KW","996","KG","371","LV","961","LB","266","LS","231","LR","423","LI","370","LT","352","LU","261","MG","265","MW","60","MY","960","MV","223","ML","356","MT","692","MH","596","MQ","222","MR","230","MU","262","YT","52","MX","377","MC","976","MN","382","ME","1","MS","212","MA","95","MM","264","NA","674","NR","977","NP","31","NL","599","AN","687","NC","64","NZ","505","NI","227","NE","234","NG","683","NU","672","NF","1","MP","47","NO","968","OM","92","PK","680","PW","507","PA","675","PG","595","PY","51","PE","63","PH","48","PL","351","PT","1","PR","974","QA","40","RO","250","RW","685","WS","378","SM","966","SA","221","SN","381","RS","248","SC","232","SL","65","SG","421","SK","386","SI","677","SB","27","ZA","500","GS","34","ES","94","LK","249","SD","597","SR","268","SZ","46","SE","41","CH","992","TJ","66","TH","228","TG","690","TK","676","TO","1","TT","216","TN","90","TR","993","TM","1","TC","688","TV","256","UG","380","UA","971","AE","44","GB","1","US","598","UY","998","UZ","678","VU","681","WF","967","YE","260","ZM","263","ZW","591","BO","673","BN","61","CC","243","CD","225","CI","500","FK","44","GG","379","VA","852","HK","98","IR","44","IM","44","JE","850","KP","82","KR","856","LA","218","LY","853","MO","389","MK","691","FM","373","MD","258","MZ","970","PS","872","PN","262","RE","7","RU","590","BL","290","SH","1","KN","1","LC","590","MF","508","PM","1","VC","239","ST","252","SO","47","SJ","963","SY","886","TW","255","TZ","670","TL","58","VE","84","VN","284","VG","340","VI","678","VU","681","WF","685","WS","967","YE","262","YT","27","ZA","260","ZM","263","ZW"] var keys = [String]() var values = [String]() let whitespace = NSCharacterSet.decimalDigits //let range = phrase.rangeOfCharacterFromSet(whitespace) for i in x { // range will be nil if no whitespace is found // if (i.rangeOfCharacterFromSet(whitespace) != nil) { if (i.rangeOfCharacter(from: whitespace, options: String.CompareOptions.caseInsensitive) != nil) { values.append(i) } else { keys.append(i) } } let countryCodeListDict:NSDictionary = NSDictionary(objects: values as [String], forKeys: keys as [String] as [NSCopying]) if let _: AnyObject = countryCodeListDict.value(forKey: country.uppercased()) as AnyObject { return countryCodeListDict[country.uppercased()] as! String } else { return "" } } else { return "" } } }
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") } }
Check if AnyObject is empty or does not have a key or if value for that key is empty
In core data, I have an entity called "CachedRecipes" and in it an attribute called "jsonData". I have a function to get JSON data from this attribute func getJSONFromCoreData()->AnyObject { var jsonDataInCoreData:AnyObject = "" do { let fetchRequest = NSFetchRequest(entityName: "CachedRecipes") let fetchedResults = try self.sharedContext.executeFetchRequest(fetchRequest) for (var i=0; i < fetchedResults.count; i++) { let single_result = fetchedResults[i] let out = single_result.valueForKey("jsonData") print(out) jsonDataInCoreData = out! } } catch { print("error") } return jsonDataInCoreData } I am using a statement in viewDidLoad of the same UIViewController to get the data like this: let jsonDataFromCoreData = self.getJSONFromCoreData() How can I check if jsonDataFromCoreData is empty or it doesn't have any key called jsonData and that key doesn't have any value? I need to print out an error if it happens.
Change your function so it returns an optional func getJSONFromCoreData()->AnyObject? { var jsonDataInCoreData:AnyObject? do { let fetchRequest = NSFetchRequest(entityName: "CachedRecipes") let fetchedResults = try self.sharedContext.executeFetchRequest(fetchRequest) for (var i=0; i < fetchedResults.count; i++) { let single_result = fetchedResults[i] let out = single_result.valueForKey("jsonData") print(out) jsonDataInCoreData = out! } } catch { print("error") } return jsonDataInCoreData } And if it returns nil then it doesn't contain that data. You can just unwrap like this: if let json = getJSONFromCoreData() { // Has some data } else { // No data }