I am trying to delete a document from Firestore that appears as a UITableViewCell on my UITableView using the swipe to delete function.
var sourseArray : [Sourse] = []
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let sourseItem = sourseArray[indexPath.row]
Firestore.firestore().collection("sourses").document(sourseItem.documentId).delete(completion: { (error) in
if let error = error {
debugPrint("Could not delete thought: \(error.localizedDescription)")
}
})
}
}
When I swipe and hit the "delete" button. This error appears
*** Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Invalid document reference.
Document references must have an even number of segments, but sourses
has 1'
I adjusted the "rules" of my Firestore database to allow for deleting.
After some research it appears that I'm not referencing the correct document somehow. Is it a bad reference or is the error something else?
Also here is what a "sourse" model is.
class Sourse {
private(set) var name: String!
private(set) var content: String!
private(set) var timeStamp: Date!
private(set) var documentId: String!
private(set) var userId: String!
init(name: String, timeStamp: Date, content: String, documentId: String, userId: String) {
self.name = name
self.content = content
self.timeStamp = timeStamp
self.documentId = documentId
self.userId = userId
}
}
//EDIT
I just noticed I did not add a documentId when creating a new sourse. As seen below.
#IBAction func addSourse(_ sender: Any) {
Firestore.firestore().collection(SOURSES_REF).addDocument(data: [
NAME : sourseTextField.text ?? "",
CONTENT : contentTextField.text ?? "",
TIMESTAMP : FieldValue.serverTimestamp(),
USERNAME : Auth.auth().currentUser?.displayName ?? "",
USER_ID : Auth.auth().currentUser?.uid ?? ""
]) { (err) in
if let err = err {
debugPrint("Error adding document document: \(err)")
} else {
self.navigationController?.popViewController(animated: true)
}
}
}
However, that is also the way it was in my tutorial and it worked fine.
///Edit 2 To show how I am fetching it.
func loadData() {
db.collection("sourses").getDocuments() { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let content = data["content"] as? String ?? ""
let timeStamp = data["timeStamp"] as? Date ?? Date()
let documentId = data["documentId"] as? String ?? ""
let userId = data["userId"] as? String ?? ""
let newSourse = Sourse(name:name, timeStamp: timeStamp, content:content, documentId: documentId, userId: userId)
self.sourseArray.append(newSourse)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
Answer: The function to swipe to delete was correct the whole time. As Dopapp pointed out, I was incorrectly loading my document.Id.
If the problem is indeed that your documentId is wrong, you may be retrieving it incorrectly. Here is a quick example of how to create your object with the right id:
collection.addSnapshotListener { (snapshot, error) in
if let documents = snapshot?.documents {
for document in documents {
guard let data = document.data() else { continue }
let id = document.documentID
let sourseItem = Sourse(name: data['name'], ..., documentId: id, ...)
// use sourseItem
}
}
}
If you are doing something similar, I would check if the document ids are being swapped between objects. If so, that might suggest an async-related problem.
For your particular case, loadData() should look like this:
func loadData() {
db.collection("sourses").getDocuments() { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let content = data["content"] as? String ?? ""
let timeStamp = data["timeStamp"] as? Date ?? Date()
let documentId = document.documentID
let userId = data["userId"] as? String ?? ""
let newSourse = Sourse(name:name, timeStamp: timeStamp, content:content, documentId: documentId, userId: userId)
self.sourseArray.append(newSourse)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
Related
How do I take the name of the Firebase document, directly from the information of the logged in user?
Basically I have a user collection and a Degree Course collection.
When I use the func
GetCorsodiLaurea
I don't want to manually insert the document name in .document ("")
But I would like to automatically take the name of the document directly from the user's info
The field that declares which course is connected to the user is "TipoCorso".
As you can see in the Degree Courses collection there is the value of the "TipoCorso" field
Here is the code of the function and a screen of the Firebase Database:
import SwiftUI
import Firebase
import FirebaseFirestoreSwift
import FirebaseDatabase
class ProfileViewModel : ObservableObject{
#Published var userInfo = UserModel(Nome: "", Cognome: "", photoURL: "", Nomeintero: "", Corsodilaurea: "", Tipocorso: "")
#Published var userDegree = userDegreeModel(Name: "", TotalSubjects: "")
var ref = Firestore.firestore()
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
fetchUser()
GetCorsodiLaurea()
}
func fetchUser() {
ref.collection("users").document(uid).getDocument { [self] (doc, err) in guard let user = doc else { return }
let Nome = user.data()?["Nome"] as! String
let Cognome = user.data()?["Cognome"] as! String
let photoURL = user.data()?["photoURL"] as! String
let Nomeintero = user.data()?["Nomeintero"] as! String
let Tipocorso = user.data()?["Tipocorso"] as! String
let Corsodilaurea = user.data()?["Corsodilaurea"] as! String
DispatchQueue.main.async {
self.userInfo = UserModel(Nome: Nome, Cognome: Cognome, photoURL: photoURL, Nomeintero: Nomeintero, Corsodilaurea: Corsodilaurea, Tipocorso: Tipocorso) }
}
}
func GetCorsodiLaurea() {
db.collection("DegreeCourses").document(self.userInfo.Tipocorso).getDocument { [self] (doc, err) in guard let DegreeCourses = doc else { return }
let Name = DegreeCourses.data()?["Name"] as! String
let TotalSubjects = DegreeCourses.data()?["TotalSubjects"] as! String
// [END doc_reference]
// [END collection_reference]
DispatchQueue.main.async {
self.userDegree = userDegreeModel(Name: Name, TotalSubjects: TotalSubjects)
}
}
}
}
User
DegreeCourses
When you call the fetchUeser() function it looks like you are populating the UserModel with the specific user's Tipocorso.
So in the GetCorsodiLaurea function you can call Tipocorso member in userInfo variable.
ref.collection("DegreeCourses").document(self.userInfo.Tipocorso).getDocument { [self] (doc, err) in guard let DegreeCourses = doc else { return }
Edit: You are most likely getting the error because the fetchUsers() function hasn't completed fully (as it is waiting for Firebase to respond) but the execution has already proceeded to the GetCorsodiLaurea() function.
To fix this add, a escaping closure to the fetchUsers() function and call the GetCorsodiLaurea() function in the closure. This way, the compiler won't try and execute the functions asynchronously.
import SwiftUI
import Firebase
import FirebaseFirestoreSwift
import FirebaseDatabase
class ProfileViewModel : ObservableObject{
#Published var userInfo = UserModel(Nome: "", Cognome: "", photoURL: "", Nomeintero: "", Corsodilaurea: "", Tipocorso: "")
#Published var userDegree = userDegreeModel(Name: "", TotalSubjects: "")
var ref = Firestore.firestore()
let uid = Auth.auth().currentUser!.uid
let db = Firestore.firestore()
init() {
//GetCorsodilaurea() will only get called after fetchUser() is complete
fetchUser(completion: {
GetCorsodiLaurea()
})
}
func fetchUser(completion: #escaping () -> Void)) {
ref.collection("users").document(uid).getDocument { [self] (doc, err) in guard let user = doc else { return }
let Nome = user.data()?["Nome"] as! String
let Cognome = user.data()?["Cognome"] as! String
let photoURL = user.data()?["photoURL"] as! String
let Nomeintero = user.data()?["Nomeintero"] as! String
let Tipocorso = user.data()?["Tipocorso"] as! String
let Corsodilaurea = user.data()?["Corsodilaurea"] as! String
//don't do this async
self.userInfo = UserModel(Nome: Nome, Cognome: Cognome, photoURL: photoURL, Nomeintero: Nomeintero, Corsodilaurea: Corsodilaurea, Tipocorso: Tipocorso)
completion()
}
}
func GetCorsodiLaurea() {
db.collection("DegreeCourses").document(self.userInfo.Tipocorso).getDocument { [self] (doc, err) in guard let DegreeCourses = doc else { return }
let Name = DegreeCourses.data()?["Name"] as! String
let TotalSubjects = DegreeCourses.data()?["TotalSubjects"] as! String
// [END doc_reference]
// [END collection_reference]
DispatchQueue.main.async {
self.userDegree = userDegreeModel(Name: Name, TotalSubjects: TotalSubjects)
}
}
}
I'm using Swift and Firestore database to implement an app like Twitter.
I want to add sweet (it's like tweet) when button is clicked to the database. And then display it in the tableview.
The data is added to the database. But is not displayed in the tableview. So when I run an app I see empty tableview.
Please help!!
TableViewController file:
import UIKit
import FirebaseFirestore
import Firebase
class TableViewController: UITableViewController {
var db:Firestore!
var sweetArray = [Sweet]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
loadData()
}
func loadData() {
db.collection("sweets").getDocuments() {
querySnapshot, error in
if let error = error {
print("Error loading documents to the db: \(error.localizedDescription)")
} else {
self.sweetArray = querySnapshot!.documents.flatMap({Sweet(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
#IBAction func composeSweet(_ sender: Any) {
let composeAlert = UIAlertController(title: "New Sweet", message: "Enter your name and message", preferredStyle: .alert)
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Your name"
}
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Your message"
}
composeAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
composeAlert.addAction(UIAlertAction(title: "Send", style: .default, handler: { (action:UIAlertAction) in
if let name = composeAlert.textFields?.first?.text, let content = composeAlert.textFields?.last?.text {
let newSweet = Sweet(name: name, content: content, timeStamp: Date())
var ref:DocumentReference? = nil
ref = self.db.collection("sweets").addDocument(data: newSweet.dictionary) {
error in
if let error = error {
print("Error adding document: \(error.localizedDescription)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
}
}))
self.present(composeAlert, animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sweetArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let sweet = sweetArray[indexPath.row]
cell.textLabel?.text = "\(sweet.name) : \(sweet.content)"
cell.detailTextLabel?.text = "\(sweet.timeStamp)"
return cell
}
}
Sweet file:
import Foundation
import FirebaseFirestore
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Sweet {
var name: String
var content: String
var timeStamp: Date
var dictionary:[String:Any] {
return [
"name": name,
"content": content,
"timeStamp": timeStamp
]
}
}
extension Sweet:DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date else {return nil}
self.init(name: name, content: content, timeStamp: timeStamp)
}
}
My storyboards:
My running app:
I can't provide a specific answer but I can explain how to find what the issue is.
While adding guard statements to protect your code is awesome, it can also lead to issues not being handled appropriately.
Take this piece of code from your question for example
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date else {return nil}
As you can see if there's some issue with name, content or timestamp, the guard will catch it - however, returning nil means it silently fails with no indication of the problem.
Suppose for example, that a field name was accidentally called Name instead of name - well, that's going to fail but you'd never know it.
I suggest handling fields separately to catch specific problems. Like this
let name = dictionary["name"] as? String ?? "name field not found"
let name = dictionary["content"] as? String ?? "content field not found"
let name = dictionary["timesStamp"] as? Date ?? "timestamp field not found"
This is called nil coalescing and will substitute a default value in case of nil. By then examining the incoming data, you can find the document that caused the issue. You could also do this
guard let name = dictionary["name"] as? String else { //handle the error }
in either case, you then have more data about the nature of the failure.
Seems you have data in querySnapshot but empty in sweetArray which means only one this your are parsing and mapping the data received into structs incorrectly. Modify this line to fix your issue:
self.sweetArray = querySnapshot!.documents.flatMap({Sweet(dictionary: $0.data())})
private var refernceCollection: CollectionReference!
database = Firestore.firestore()
refernceCollection = Firestore.firestore().collection(kMessages)
func fetchData() {
refernceCollection.addSnapshotListener{ snapshots, error in
if error != nil {
print("error --->>")
} else {
guard let snap = snapshots else { return }
var arrUser:[MDLMessages] = []
for documet in snap.documents {
let data = documet.data()
let message = data["message"] as? String ?? "This message was deleted"
let time = data["time"] as? Date ?? Date.now
let documentId = documet.documentID
let userId = data["userId"] as? String ?? ""
let details = MDLMessages(message: message, time: time, documentId: documentId, userId: userId)
arrUser.append(details)
}
DispatchQueue.main.async {
self.arrMessages = arrUser
self.tblChatDetails.reloadData()
}
}
}
}
I have my data structure: My Firestore Database
As you'll see I have a "Michael 201A" document as well as a "Michael 201B" the idea is to retrieve the fields from these documents and display them in a tableView. Additionally, i would like the tableView to update automatically based off of any new documents that are added to the "Requests" Collection so the tableView data is always populated wit the most recent additions to the firestore database.
Function to retrieve data from FireStore
#IBOutlet weak var tableView: UITableView!
var db: Firestore!
var requestArray = [Request]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
tableView.delegate = self
tableView.dataSource = self
loadData()
}
func loadData() {
db.collection("Requests").whereField("Status", isEqualTo: true).getDocuments() {(querySnapshot, err) in
if let err = err {
print("An error occurred\(err)")
} else{
self.requestArray = querySnapshot!.documents.compactMap({Request(dictionary: $0.data())})
print(self.requestArray)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
I've added a print statement to get a reading of the value but it returns empty.
My tableView functions
extension ResidentAdvisorViewController: UITableViewDelegate {
func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped me")
}
}
extension ResidentAdvisorViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return requestArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let request = requestArray[indexPath.row]
cell.textLabel?.text = "\(request.Name)"
return cell
}
}
My Request Struct
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Request {
var Name: String
var Dorm: String
var Room: Int
var Status: Bool
var UID: String
var TimeStamp: Date
var dictionary:[String:Any] {
return [
"Name":Name,
"Dorm":Dorm,
"Room":Room,
"Status":Status,
"UID": UID,
"TimeStamp": TimeStamp
]
}
}
extension Request : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["Name"] as? String,
let dorm = dictionary["Dorm"] as? String,
let room = dictionary["Room"] as? Int,
let status = dictionary["Status"] as? Bool,
let uid = dictionary["UID"] as? String,
let timestamp = dictionary["Timestamp"] as? Date
else { return nil}
self.init(Name: name, Dorm: dorm, Room: room, Status: status, UID: uid, TimeStamp: timestamp)
}
}
As a side note i have checked to ensure my cell identifier matches "cell". Also, when i change the cell text to "Hello World" I am able to get it displayed in my tableView. Any assistance is greatly appreciated thank you.
There's not a whole lot wrong with the code but there are two questions within the question.
1) Why is the value empty
2) How to I populate my dataSource intially and then update it when new documents are added.
Let me address 2) first.
To initially load the data and then watch for future changes, we can uyse the .addSnapshotListener function, and then handle the specific change type within the firebase closure.
func observeAllRequests() {
let requestsCollection = self.db.collection("Requests")
let query = requestsCollection.whereField("Status", isEqualTo: true)
query.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let name = diff.document.get("Name") as? String ?? "No Name"
print("added: \(name)") //add to your dataSource
}
if (diff.type == .modified) {
let name = diff.document.get("Name") as? String ?? "No Name"
print("modified: \(name)") //update the request in the dataSource
}
if (diff.type == .removed) {
let name = diff.document.get("Name") as? String ?? "No Name"
print("removed: \(name)") //remove the request from the dataSource
}
}
//tableView.reloadData()
}
}
The above code will return all of the documents that match the query. Iterate over the items in the snapshot, with each being either .added, .modified or .removed. The first time the function is called, all differences will be .childAdded which allows you to initially populate the dataSource.
Any document changes after that will be just the document that was changed with the difference being by .added, .modified and .removed.
EDIT:
To address question 1)
The reason the array is empty is because of how the extension is structured - it's pretty much an all or none. Here's how it is now
extension Request : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String
let dorm = dictionary["Dorm"] as? String,
let room = dictionary["Room"] as? Int,
let status = dictionary["Status"] as? Bool,
let uid = dictionary["UID"] as? String,
let timestamp = dictionary["Timestamp"] as? String
else { return nil}
self.init(Name: name)
} }
If a field is not found then the entire thing fails and returns nil, and compactMap igores nil so you end up when an empty array. Your structure does not include Timestamp, so it fails.
I would suggest something to protect your code but allow for missing fields. The nil-coalescing operator would work well here
extension Request : DocumentSerializable {
init?(dictionary: [String : Any]) {
let name = dictionary["name"] as? String ?? "No Name"
let room = dictionary["room") as? String ?? "No Room"
etc
How I can get and display last message in my chat?
For test, I created four users with test messages. Now I can display only last message for all users. I mark red color.
Also I use firebase to save messages and create channels.
Struct in firebase look like this:
- Chats
- channel id
- document data (then be stored ID and NAME of channel)
- collection thread
- documents data (then be stored MESSAGES)
My struct in channel:
struct Channel {
let id: String?
let name: String
init(name: String) {
id = nil
self.name = name
}
init?(document: DocumentSnapshot) {
let data = document.data()!
guard let name = data["name"] as? String else {
return nil
id = document.documentID
self.name = name
}
}
extension Channel: DatabaseRepresentation {
var representation: [String : Any] {
var rep = ["name": name]
if let id = id {
rep["id"] = id
}
return rep
}
}
And my struct message, I use MessageKit:
struct Message: MessageType {
let id: String?
let content: String
let sentDate: Date
let sender: SenderType
var kind: MessageKind {
if let image = image {
return .photo(ImageMediaItem.init(image: image))
} else {
return .text(content)
}
}
var messageId: String {
return id ?? UUID().uuidString
}
var image: UIImage? = nil
var downloadURL: URL? = nil
init(profile: Profile, content: String) {
sender = Sender(id: profile.id, displayName: profile.name)
self.content = content
sentDate = Date()
id = nil
}
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let sentDate = (data["created"] as? Timestamp)?.dateValue() else {
return nil
}
guard let senderID = data["senderID"] as? String else {
return nil
}
guard let senderName = data["senderName"] as? String else {
return nil
}
id = document.documentID
self.sentDate = sentDate
sender = Sender(id: senderID, displayName: senderName)
if let content = data["content"] as? String {
self.content = content
downloadURL = nil
} else if let urlString = data["url"] as? String, let url = URL(string: urlString) {
downloadURL = url
content = ""
} else {
return nil
}
}
}
extension Message: DatabaseRepresentation {
var representation: [String : Any] {
var rep: [String : Any] = [
"created": sentDate,
"senderID": sender.senderId,
"senderName": sender.displayName
]
if let url = downloadURL {
rep["url"] = url.absoluteString
} else {
rep["content"] = content
}
return rep
}
}
For load my chennels I use code below:
fileprivate func observeQuery() {
guard let query = query else { return }
listener = query.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")")
return
}
snapshot.documentChanges.forEach { (change) in
self.handleDocumentChange(change)
}
}
}
private func handleDocumentChange(_ change: DocumentChange) {
guard let channel = Channel(document: change.document) else {
return
}
switch change.type {
case .added:
addChannelToTable(channel)
case .modified:
updateChannelInTable(channel)
case .removed:
removeChannelFromTable(channel)
}
}
private func addChannelToTable(_ channel: Channel) {
guard !channels.contains(channel) else {
return
}
channels.append(channel)
channels.sort()
guard let index = channels.index(of: channel) else {
return
}
tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
private func updateChannelInTable(_ channel: Channel) {
guard let index = channels.index(of: channel) else {
return
}
channels[index] = channel
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
private func removeChannelFromTable(_ channel: Channel) {
guard let index = channels.index(of: channel) else {
return
}
channels.remove(at: index)
tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
I think need update my Channel struct. But how to do it?
And how to correct load and display last message from firebase?
If need more info pls tell me, I will update my question.
If the question is how to get only the last message from Firestore, you need to define how to determine what the last message is. That's usually done via a timestamp - the latest timestamp will be the last message.
The structure in the question is a little unclear so let me provide a simple example.
messages //collection
document_0 //documentID auto-generated
msg: "Last Message"
timestamp: "20191201"
document_1
msg: "First message"
timestamp: "20190801"
document_2
msg: "A message in the middle"
timestamp: "20191001"
As you can see, no matter what order they are written to Firestore, it's clear that the one with the latest timestamp (20191201 ) is the last message.
To get the last message we need a query that does two things:
1) Query the messages node, sort descending, which will put the last message 'at the top'
2) Limit the query to 1, which will get that message.
func readLastMessage() {
let ref = Firestore.firestore().collection("messages").order(by: "timestamp", descending: true).limit(to: 1)
ref.getDocuments(completion: { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
if let doc = snapshot.documents.first {
let docID = doc.documentID
let msg = doc.get("msg")
print(docID, msg)
}
})
}
and the output
Last Message
The above code gets the last message but could be expanded upon by adding an observer instead of getDocuments in the same fashion that will notify the app when there's a new last message.
I am attempting to show my Firestore data into my Tableview but I can't seem to get it to show up.
protocol DocumentSerializeable {
init?(dictionary:[String:Any])
}
struct Sourse {
var name: String
var content: String
var timeStamp: Date
var dictionary: [String: Any] {
return [
"name": name,
"content": content,
"timestamp": timeStamp
]
}
}
extension Sourse : DocumentSerializeable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date else {return nil}
self.init(name: name, content: content, timeStamp: timeStamp)
}
}
class SourseListTableViewController: UITableViewController {
var db: Firestore!
var sourseArray = [Sourse]()
private var document: [DocumentSnapshot] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
//initalize Database
db = Firestore.firestore()
loadData()
}
At first I tried this code below, there were no errors but nothing loaded in the tableview.
func loadData() {
db.collection("sourses").getDocuments() {
snapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
self.sourseArray = snapshot!.documents.flatMap({Sourse(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
After some research (reading from Firestore - Append to tableView when view is loaded ) I tried this code below, but I get the error "Cannot convert value of type '(name: String, content: String, timeStamp: Date?)' to expected argument type 'Sourse'" So I tried it with the date removed from all the code and I still couldn't get it to work.
func loadData() {
db.collection("sourses").getDocuments() {
snapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
for document in snapshot!.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let content = data["content"] as? String ?? ""
let timeStamp = data["timeStamp"] as? Date
let newSourse = (name:name, content:content, timeStamp: timeStamp)
self.sourseArray.append(newSourse)
}
}
}
}
Here is my numberOfRows/CellForRow to make sure its not the tableview itself. I've also double checked the "cell identifier" with my storyboard.
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return sourseArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SourseTableViewCell", for: indexPath)
let sourse = sourseArray[indexPath.row]
cell.textLabel?.text = "\(sourse.name)"
cell.detailTextLabel?.text = "\(sourse.content)"
return cell
}
You need to reload your tableView after parsing your Snapshot. It is also not a good idea to force unwrap your Snapshot:
.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let content = data["content"] as? String ?? ""
let timeStamp = data["timeStamp"] as? Date ?? Date()
let newSourse = Sourse(name:name, content:content, timeStamp: timeStamp)
self.sourseArray.append(newSourse)
}
self.tableView.reloadData()
}
}
rbaldwin talked me through it, his answer is correct, I'm just posting the full loaddata function for the record.
func loadData() {
db.collection("sourses").getDocuments() { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let content = data["content"] as? String ?? ""
let timeStamp = data["timeStamp"] as? Date ?? Date()
let newSourse = Sourse(name:name, content:content, timeStamp: timeStamp)
self.sourseArray.append(newSourse)
}
self.tableView.reloadData()
}
}
}
}