Reset Core Data Bool for All Objects - swift

I would like to set the bool value to false for every object in my core data entity.
This is how I am currently setting an individual value in core data:
func updateFriendSaidHi(friend: Friends, in context: NSManagedObjectContext = CoreDataStack.shared.container.newBackgroundContext()){
context.performAndWait {
// Update value to true in core data
friend.saidHi = true
saveToPersistentStore()
}
}
You could loop through each item in the entity but I'm guessing there's a better way to do this.
Any input would be great.

You could change the attributes of all records with Key-Value Coding
let request = NSFetchRequest<Friends> = Friends.fetchRequest()
context.performAndWait {
do {
let friends = context.fetch(request)
(friends as NSArray).setValue(true, forKey:"saidHi")
saveToPersistentStore()
} catch { print(error) }
}

Related

How I can get all of users lists from Firestore?

class UsersViewModel : ObservableObject {
#Published var users = [CurrentUser]()
init() {
fetchUserLists()
print(users)
}
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users")
.getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
//success
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
}
}
}
I'm trying to fetch all of users in my firestore database, but unfortunately my users array is empty. I don't know what my mistake is.
Please check my firestore screen shot, and give me tips!
Thank you!
You're having an issue with asynchronous code. Code is faster than the internet and you have to allow time for data to be retrieved from Firebase.
Additionally, Firebase data is only valid within the closure following the Firebase call. In this case your code is attempting to print an array before it's been filled.
Here's the issue
init() {
fetchUserLists() //<-takes time to complete
print(users) //this is called before fetchUserLists fills the array
}
here's the fetchUserLists function with where the print statement should be
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users").getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
print(self.users) //we are within the closure and the array is now populated
//this is a good spot to, for example, reload a tableview
// or update other UI elements that depend on the array data
}
}

How can I add these Firestore fields to a Dictionary?

I am looking to add all my "usernames" into a dictionary. I am having some trouble doing this. I am sure it's very obvious, but I am very new to coding.
I am stuck at, right now and can't seem to find a clear answer anywhere:
func fetchUser() {
let db = Firestore.firestore()
let usernameSearch = db.collection("users")
usernameSearch.getDocuments { (snapshot, error) in
if error != nil {
print("Error obtaining usernames")
} else {
for field in snapshot!.documents {
let field = field.get("username")
print(field!)
}
}
}
}
I would really appreciate it if somebody could help me out. I am sure it's very obvious, or I'm just doing it totally wrong.
First, get into the habit of safely unwrapping over force unwrapping. And choose more accurate names for your objects (i.e. usersCollection over usernameSearch). However, in this case, there's no need to instantiate individual properties for the database and the collection since they're not being used anywhere else but here (so be efficient and omit them).
var usersDictionary = [String: [String]]()
func fetchUser() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot { // unwrap the snapshot safely
var usernames = [String]()
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
usernames.append(username)
}
}
usersDictionary["usernames"] = usernames
} else {
if let error = error {
print(error)
}
}
}
}
Or if you actually meant an array of users:
var usersArray = [String]()
func fetchUser() {
Firestore.firestore().collection("users").getDocuments { (snapshot, error) in
if let snapshot = snapshot { // don't force unwrap with !
for doc in snapshot.documents {
if let username = doc.get("username") as? String {
usersArray.append(username)
}
}
} else {
if let error = error {
print(error)
}
}
}
}
I'm assuming that what you're looking for is an Array, not a Dictionary. I'll also assume that you are indeed getting the correct value that you'd expect out of field.get("username"), e.g. a string such as "Bob." Therefore, what you are trying to do is map the list of document objects to a list of strings.
If you scroll to the Topics section of the Array documentation from Apple, you can find some of the operations they provide for arrays such as snapshot!.documents.
One of those operations is actually map, and its description is:
Returns an array containing the results of mapping the given closure over the sequence’s elements.
https://developer.apple.com/documentation/swift/array/3017522-map
In other words, you provide a transformation to perform for each instance of a document belonging to the snapshot!.documents Array and get back a new Array containing the resultant values of that transformation.
In this case I will use a more specific operation; compactMap. We have to try and cast the returned value from Any to String. If that does not succeed, it will return nil, and we'll want to filter that out. I expect it to be an unlikely case due to the type requirements made by the Firebase Console, but it's good to be aware of it. Here is the example:
func fetchUsernames(from usernameCollection: String, completion: #escaping ([String]) -> Void) {
let db = Firestore.firestore()
let collection = db.collection(usernameCollection)
collection.getDocuments { snapshot, error in
guard error != nil,
let usernames = snapshot?.documents.compactMap { $0.get("username") as? String }
else { return print("Error obtaining usernames") }
completion(usernames)
}
}
The key line here being let usernames = snapshot?.documents.compactMap { $0.get("username") }. We are passing the map function a closure. This closure is passed an argument itself; each value from the snapshot?.documents array. You may refer to this passed in value with $0.

How do I build a custom object that consists of 2 custom objects for an expanding cell?

I am following along this tutorial here for collapsing UITableViewCells and the mechanics are quite straight forward but I am not quite sure how to populate my model arrays from Firestore. He has manually created the data for demo purposes so naturally as a beginner, I am stumbling since instead of that I am making a network call to Firebase.
My data structure is simple. The base collection (which would populate the title of the cell) extracts data from here: db.collection("Insurance_Plans") and contains the following strings:
- Holder name
- Holder contact etc.
And each insurance holder has multiple properties insured and this is the sub-collection i.e. db.collection("Insurance_Plans").document(planId).("Insured_Property") and data model consists of strings such as:
- Property type
- Property address etc.
What I am doing is creating the main struct:
struct cellData {
var opened = Bool()
var plans = [Plan]()
var properties = [Properties]()
}
and in the class itself I declare an instance of it it as:
var tableViewData = [cellData]()
Then I query the insurance meta data (which has its own function) as follows:
db.collection("Insurance_Plans").getDocuments() {
documents, error in
guard let snapshot = documents else {
let error = error
print("Error fetching documents results: \(error!.localizedDescription)")
return
}
let results = snapshot.documents.map { (document) -> Plan in
if let plan = Plan(dictionary: document.data(), id: document.documentID) {
self.plansArray.append(plan)
self.loadPropertyData(planId: document.documentID) // another function where property details are queried
return plan
} else {
fatalError("Unable to initialize type \(Plan.self) with dictionary \(document.data())")
}
}
self.plansArray = results
self.plansDocuments = snapshot.documents
self.plansTableView.reloadData()
}
Then I query the properties in each plan as such:
db.collection("Insurance_Plans").document(planId).collection("Properties").getDocuments() { documents, error in
guard let snapshot = documents else {
let error = error
print("Error fetching documents results: \(error!.localizedDescription)")
return
}
let results = snapshot.documents.map { (document) -> Property in
if let property = Property(dictionary: document.data()) {
return property
} else {
fatalError("Unable to initialize type \(Property.self) with dictionary \(document.data())")
}
}
self.propertiesArray = results
self.propertiesDocuments = snapshot.documents
self.ordersTableView.reloadData()
}
My question then is how do I enter the insurance meta data and the subsequent properties data into a cellData object and where do I do this?

Core Data Object was written to, but never read

As I try to update an existing entry in my Core Data DB, I fetch the desired item by id, change it to a new item and save in context.
However, when I fetch the object and replace it, I get the warning "Core Data Object was written to, but never read." It does make sense since I'm not really using that object, but as I understand it, just giving it a value saves it in Core Data.
static var current: User? {
didSet {
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
CoreDataManager.saveInContext()
}
}
static func get(with id: String?) -> User? {
guard let id = id else { return nil }
let request: NSFetchRequest = User.fetchRequest()
let predicate = NSPredicate(format: "id = %#", id)
request.predicate = predicate
do {
let users = try CoreDataManager.managedContext.fetch(request)
return users.first
} catch let error {
print(error.localizedDescription)
return nil
}
}
I want to make sure, is this the recommended process to overwrite a value in Core Data, or am I doing something wrong?
This section
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
seems just updating local variable userInCoreData, not User object in Core Data.
So the warning says "you fetched data from core data and set to a variable, but you set another value to the variable soon, never use the first value from core data. Is it OK?"
What you really want to do is something like this?
if var userInCoreData = User.get(with: current?.id), let current = current {
userInCoreData.someValue = current.someValue
userInCoreData.anotherValue = current.anotherValue
}

How to Save to a Custom Join Table Core Data (many-to-many) without unique predicate - ManagedObjectID (Swift)?

I will be super thankful for any help. How can I save instances to a join table without a unique identifier as a predicate? Can I use the managed object id to check if the item exists already?
I'm building an app with different exercise plans. Each plan holds many exercise, and an exercise can belong to many plans. I have structured my data model to include a custom join table so that I can query the completion status of an exercise from within one plan.
I'm sourcing my data from a json file and would like to save it to core data. I'm able to correctly save my CoreExercise, and CorePlan tables, however am having difficulty understanding how to save the instance of the object in the intermediate join table, since I'm unsure of what predicate to use.
I've written a class function to check if the instance exists, and to save it if it doesn't.
class CoreExercisePlan: NSManagedObject {
class func coreExercisesForExercisePlan(exerciseInfo: Exercise, planName: String, inManagedObjectContext context: NSManagedObjectContext) -> CoreExercisePlan? {
let request = NSFetchRequest(entityName: "CoreExercisePlan")
request.predicate = NSPredicate() // Search for ObjectID here? / How?
if let exercisePlan = (try? context.executeFetchRequest(request))?.first as? CoreExercisePlan {
print("we have this exercise plan already saved")
return exercisePlan
} else if let exercisePlan = NSEntityDescription.insertNewObjectForEntityForName("CoreExercisePlan", inManagedObjectContext: context) as? CoreExercisePlan {
exercisePlan.status = 0
exercisePlan.progress = 0
print("we are creating new object")
return exercisePlan
}
return nil
}
private func updateDatabaseWithExercisePlans(){
managedObjectContext?.performBlock {
// Array of exercises for each plan:
let coffeePlanExercises = self.coffeeExercises
let subwayPlanExercises = self.subwayExercises
for exercise in coffeePlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "coffee", inManagedObjectContext: self.managedObjectContext!)
}
for exercise in subwayPlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "subway", inManagedObjectContext: self.managedObjectContext!)
}
do {
try self.managedObjectContext?.save()
} catch let error {
print("printing error here: \(error)")
}
}
}
Is there a way to get the objectID of the instance in the join table, and use that as a predicate? Thanks!