Reputation: 5
I'm trying to do small apps containing firebase codes to learn more about it. so here I made a todo list app, I was able to add the tasks to the firebase and I was able to delete it, the problem I have is updating the status of the task (isComplete: Bool) I've no idea how to write firebase code to update data. almost all the tutorial I read was about the data uploaded to real-time database and I'm using the cloud so I couldn't figure it out. here I wrote this code so when the task is done and I select the cell the circle turn into checkmark.circle It's work but of course the database isn't updated..
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TodoCell
if cell.isComplete == false{
cell.doneButton.image = UIImage(systemName: "checkmark.circle")
cell.isComplete = true
} else {
cell.doneButton.image = UIImage(systemName: "circle")
cell.isComplete = false
}
}
}
Adding tasks to firebase codes
public func postTask(task:String, isComplete: Bool,
completion: @escaping (Result<Bool, Error>) -> ()) {
guard let user = Auth.auth().currentUser else {
return
}
let documentRef = db.collection(DatabaseService.itemsCollection).document()
db.collection(DatabaseService.usersCollection).document(user.uid).
collection(DatabaseService.tasksCollection).
document(documentRef.documentID).setData(["task" : task,
"isComplete": isComplete,
"taskId": documentRef.documentID])
{ (error) in
if let error = error {
completion(.failure(error))
} else {
completion(.success(true))
}
}
}
SnapshotListener
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
guard let user = Auth.auth().currentUser else {
return
}
listener = Firestore.firestore().collection(DatabaseService.usersCollection)
.document(user.uid).collection(DatabaseService.tasksCollection)
.addSnapshotListener({ [weak self] (snapshot, error) in
if let error = error {
DispatchQueue.main.async {
self?.showAlert(title: "Try Again", message:
error.localizedDescription)
}
} else if let snapshot = snapshot {
let task = snapshot.documents.map { TasksList($0.data()) }
self?.todoItems = task
}
})
}
based on @bkbkchoy answer I wrote these codes:
func updateTask(task: TasksList,
isComplete: Bool,
completion: @escaping (Result<Bool, Error>) -> ()) {
guard let user = Auth.auth().currentUser else { return }
db.collection(DatabaseService.usersCollection).document(user.uid)
.collection(DatabaseService.tasksCollection).document(task.taskId)
.updateData(["isComplete": isComplete]) { (error) in
if let error = error {
completion(.failure(error))
} else {
completion(.success(true))
}
}
}
}
and under didSelectRow
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TodoCell
let isComplete = false
if task2.isComplete == false{
cell.doneButton.image = UIImage(systemName: "checkmark.circle")
cell.doneButton.tintColor = .systemBlue
cell.isComplete = true
} else {
cell.doneButton.image = UIImage(systemName: "circle")
cell.doneButton.tintColor = .systemGray
cell.isComplete = false
}
updateStatus(isComplete: isComplete)
}
private func updateStatus(isComplete: Bool) {
databaseService.updateTask(task: task2, isComplete: isComplete)
{ [weak self] (result) in
switch result {
case .failure(let error):
DispatchQueue.main.async {
self?.showAlert(title: "Try again", message: error.localizedDescription)
}
case .success:
break
}
}
}
}
but I got an error :
No document to update: project/todo-list/database/(default)/documents/users/jYZmghQeXodeF2/tasks/1
struct TasksList {
let task: String
let taskId: String
let isComplete: Bool
}
extension TasksList {
init(_ dictionary: [String: Any]) {
self.task = dictionary["task"] as? String ?? ""
self.taskId = dictionary["taskId"] as? String ?? ""
self.isComplete = dictionary["isComplete"] as? Bool ?? false
}
}
Upvotes: 0
Views: 85
Reputation: 121
There are a couple of ways to update documents in Cloud Firestore:
setData
:db.collection(DatabaseService.usersCollection)
.document(user.uid)
.collection(DatabaseService.tasksCollection)
.document(task.taskId)
.setData(["isComplete": isComplete], merge: true)
Note: if you use setData
, you must include merge: true
to overwrite a single property on an existing document or else the whole document will be overwritten.
updateData
db.collection(DatabaseService.usersCollection)
.document(user.uid)
.collection(DatabaseService.tasksCollection)
.document(task.taskId)
.updateData(["isComplete": isComplete]) { err in
if let err = err {
print("error updating document: \(err)")
} else {
print("doc successfully updated")
}
}
Firestore has some great documentation online. If you want to learn more about updating/adding data here's a good place to start.
Upvotes: 1
Reputation: 285074
Your approach cannot work.
The cell is just the view, it shows the UI elements and their values, the data source is the model TasksList
(why not simply Task
).
Cells are reused and you will lose the isCompleted
information in the cell when the user scrolls. You have to update the model and reload the view
First of all declare the model as Task
and isComplete
as var
iable. According to the naming guidelines task
should be name
or title
and taskId
should be just id
struct Task {
let task: String
let taskId: String
var isComplete: Bool
}
In cellForRow
set the UI elements in the cell according to the model
let task = todoItems[indexPath.row]
let imageName = task.isComplete ? "checkmark.circle" : "circle"
cell.doneButton.image = UIImage(systemName: imageName)
cell.doneButton.tintColor = task.isComplete ? .systemBlue : .systemGray
In didSelect
toggle isComplete
in the model, reload the row and save the task
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
todoItems[indexPath.row].isComplete.toggle()
tableView.reloadRows(at: [indexPath], with: .none)
let task = todoItems[indexPath.row]
updateTask(task: task, isComplete: task.isComplete) { result in print(result) }
}
As the whole task is handed over, the second parameter isComplete
is not needed in updateTask
.
Upvotes: 0