How do I access Firestore field value without knowing the name? - swift

I am uploading data to Firestore like so:
func uploadTrackedSymptomValues(symptom: String, comment: String, time: String, timestamp: Int) {
print("Uploading symptom values.")
let user_id = FirebaseManager.shared.user_id
let docRef = db.collection("users").document(user_id!).collection("symptom_data").document("\(symptom)_data")
let updateData = [String(timestamp) : ["symptom" : symptom, "severity" : severity, "comment" : comment, "timestamp" : String(timestamp)]]
docRef.setData(updateData, merge: true)
docRef.setData(updateData, merge: true) { (err) in
if err != nil {
print(err?.localizedDescription as Any)
self.view.makeToast(err?.localizedDescription as! String)
} else {
print("Symptom Data Uploaded")
self.view.makeToast("\(symptom) logged at \(time). Severity: \(self.severity). Comment: \(comment)", duration: 2.0, position: .center, title: "Success!", image: self.cellImage) { didTap in
if didTap {
print("completion from tap")
} else {
print("completion without tap")
}
}
}
}
}
On Firestore this looks like:
Now I want to use each field under the timestamp, for a UITableViewCell But I am not sure how to access the field under the document, for example 1556998898 under Anxiety_data so I can access:
comment = "comment";
severity = "Mild";
symptom = "Anxiety";
timestamp = 1556998898;
To use in the UITableViewCell, With realtime database I would use childAdded listener. I have tried:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "EntryDataCell", for: indexPath) as! EntryDataCell
cell.configureCustomCell()
let user_id = FirebaseManager.shared.user_id
let symptomRef = db.collection("users").document(user_id!).collection("symptom_data").document(symptomSections[index])
symptomRef.getDocument(completion: { (document, err) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("\(document.documentID) : \(dataDescription)")
cell.commentLabel.text = dataDescription //<- Stuck here
} else {
print("Document does not exist")
}
})
return cell
}
But I am stuck as to how I would get the specific field, as I don't know the name as it is the timestamp of when it was made, rather than the whole document. If there is a better way to handle this please let me know. Thank you.
EDIT:
I am able to get the Key (Timestamp) and Values but am having trouble parsing the values:
let obj = dataDescription
for (key, value) in obj! {
print("Property: \"\(key as String)\"") //<- prints Timestamp
print("Value: \"\(value)\"") // <- prints the comment,severity,symptom,timestamp fields I need.
}
If I try to do cell.titleLabel.text = value["comment"] I get Value of type 'Any' has no subscripts
I thought about using a struct:
struct FieldValues: Codable {
let comment: String
let severity: String
let symptom: String
let timestamp: Int
}
But am unsure on how to use it with the value. I feel I am in a bit over my head for this last part.

Related

swift Lost data from the realm database in another country

I use the realm database for my application (to-do list), everything works fine, BUT once I flew to another country and noticed that the records in the database are empty (the application gives out an empty list), upon arrival back to my country everything returned to normal ... Now I am again in a different country and the situation repeats again (database is empty), for some reason the database gives an empty list result, can you please explain why this is happening and how to fix that?
Output example
var dbToDoList = DBrealmToDoList()
var arrayToDoList: Results<RealmToDoList> {
get {
return dbToDoList.getArray()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
dbToDoList.realm = realm
let current = arrayToDoList.filter { (_todo) -> Bool in
return _todo.date == date
}.first
self.selectedDate = date
if current != nil {
self.selectedLists = current?.lists
self.selectedListsSorted = self.selectedLists?.sorted(by: { (val, val2) -> Bool in
return (!val.value && val2.value)
})
}
}
And then in tableView I display the data from the selectedListsSorted
// MARK: UITableView
extension ToDoListViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedListsSorted?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ToDoListTableViewCell
let current = selectedListsSorted?[indexPath.row]
cell.nameLabel.text = current?.key
cell.checkBox.isSelected = current?.value ?? false
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
Here is a class for working with db
class RealmToDoList: Object {
#objc private dynamic var dictionaryData: Data?
var lists: [String: Bool] {
get {
guard let dictionaryData = dictionaryData else {
return [String: Bool]()
}
do {
let dict = try JSONSerialization.jsonObject(with: dictionaryData, options: []) as? [String: Bool]
return dict!
} catch {
return [String: Bool]()
}
}
set {
do {
let data = try JSONSerialization.data(withJSONObject: newValue, options: [])
dictionaryData = data
} catch {
dictionaryData = nil
}
}
}
#objc dynamic var date : Date?
}
class DBrealmToDoList {
var realm: Realm!
func write(_ data: RealmToDoList) throws -> Bool {
var result = false
if (realm != nil) {
try! realm.write {
realm.add(data)
result = true
}
return result
} else {
throw RuntimeError.NoRealmSet
}
}
func getArray() -> Results<RealmToDoList> {
return realm.objects(RealmToDoList.self)
}
func delete(_ data: RealmToDoList) throws -> Bool {
var result = false
if (realm != nil) {
try! self.realm.write {
self.realm.delete(data)
result = true
}
return result
} else {
throw RuntimeError.NoRealmSet
}
}
func update(ofType:Object,value:AnyObject,key:String)->Bool{
do {
let realm = try Realm()
try realm.write {
ofType.setValue(value, forKeyPath: key)
}
return true
}catch let error as NSError {
fatalError(error.localizedDescription)
}
}
func filter(id:Int) -> RealmToDoList? {
let match = realm.objects(RealmToDoList.self).filter("id == %#",id).first
return match
}
func newToDoList(date : Date?,lists: [String: Bool]) -> RealmToDoList{
let pill = RealmToDoList()
pill.date = date
pill.lists = lists
return pill
}
}
I doubt that the matter is in the database, but I cannot understand what it is, because I don’t do a filter by country, etc.
The issue is the date because the date will change based on time zone and if you're selecting today's date/time in one time zone, it will be different that's what's in the database. So if a filter is based on this date
#objc dynamic var date : Date?
then that date will be "today" for whatever time zone you're in but a "today" date that was created this morning in a different time zone will not return the current time zones date.
e.g. if you create a new date/time it will be today in this timezone but could be yesterday in a different time zone.

Showing post data from logged in user only swift/xcode/firebase

Hey I have been following tutorials online to try and make a social media app and it works fine where all the posts made by users populates in one scene but in another scene I would like for the users who are currently logged in to be able to see their posts only instead of having to find their posts from a bunch of posts made by other users, below are my code samples:
Posts Code:
import Foundation
import UIKit
import Firebase
class Posts{
var id: String
var author: UserProfile
var text: String
var timestamp:Double
var createdAt:Date
init(id:String, author:UserProfile,text:String,timestamp:Double) {
self.id = id
self.author = author
self.text = text
self.timestamp = timestamp
self.createdAt = Date(timeIntervalSince1970: timestamp / 1000)
}
static func parse(_ key:String, _ data:[String:Any]) -> Posts? {
if let author = data["author"] as? [String:Any],
let uid = author["uid"] as? String,
let name = author["name"] as? String,
let photoURL = author["photoURL"] as? String,
let url = URL(string:photoURL),
let text = data["text"] as? String,
let timestamp = data["timestamp"] as? Double {
let userProfile = UserProfile(uid: uid, name: name, photoURL: url)
return Posts(id: key, author: userProfile, text: text, timestamp:timestamp)
func performDeepQuery() {
guard let user = Auth.auth().currentUser else { return }
let uid = user.uid
let ref = Database.database().reference().child("posts")
let query = ref.queryOrdered(byChild: "author/uid").queryEqual(toValue: uid)
query.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
let text = postSnap.childSnapshot(forPath: "text").value as? String ?? "No Text"
print(text)
//same as above
}
})
}
}
return nil }
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return theposts.count
case 1:
return fetchingMore ? 1 : 0
default:
return 0
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 { let cell = HomeViewControllerScene.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! Posting_TableViewCell
cell.set(theposts: theposts[indexPath.row])
return cell}
else {
let cell = HomeViewControllerScene.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 72.0
}
Please someone help me out here I have been scratching my head for a while with this!!!
I believe the question is how to get the posts for a certain user. The structure looks good but no need to have a child node 'author' in each post so instead of this:
posts
post_id_0
author
author data
text: "Hello, World"
uid: "uid_0"
do this
posts
post_id_0
text: "Hello, World"
uid: "uid_0"
name: "usmaan"
photoURL:"https://firebasestorage..."
So now just query for this users posts (this is for Firestore, scroll down fo the RTDB solution)...
func getThisUsersPosts() {
let uid = "uid_0" //this users uid
self.db.collection("posts]").whereField("uid", isEqualTo: uid).getDocuments { (snapshot, error) in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = snapshot?.documents {
for d in doc {
let text = d.get("text") as? String ?? "No Post Text"
print(text)
}
} else {
print("no posts found")
}
}
}
self.db points to my Firestore.
EDIT:
OP is using the Real Time Database so here's the code for that
func getThisUsersPosts() {
let uid = "uid_0"
let ref = self.ref.child("posts") //self.ref points to MY firebase.
let query = ref.queryOrdered(byChild: "uid").queryEqual(toValue: uid)
query.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
let text = postSnap.childSnapshot(forPath: "text").value as? String ?? "No Text"
print(text)
}
})
}
EDIT 2:
OP wants to keep their same structure.
To query for data that's two levels deep we use what's called Deep Query and will look something like this:
func performDeepQuery() {
let uid = "uid_0"
let ref = self.ref.child("posts")
let query = ref.queryOrdered(byChild: "author/uid").queryEqual(toValue: uid)
query.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
//populate your tableView datasource here
let post = PostClass()
post.postId = postSnap.key
post.name = postStap.childSnapshot("name").value as? String ?? "No Post Name"
post.text = postStap.childSnapshot("text").value as? String ?? "No Post Text"
self.postArray.append(post)
}
self.myTableView.reloadData()
})
}
which will perform a deep query on this structure
posts
post_0
author
uid: "uid_0"
name: "post 0 name"
text: "post 0 text"
The PostClass could be this
class PostClass {
var postId = ""
var name = ""
var text = ""
}
More Info:
To get the current users uid (which is covered in the getting started guide as well) and assuming you are authenticated (otherwise it will be nil)
guard let user = Auth.auth().currentUser else { return }
let uid = user.uid

How i implement my JSON API Request into a UITableViewCell?

I have a problem with my current Project. First of all, i like to implement a JSON API Request that allows me to get a title off a URL. The Problem: I want to display the JSON data into a UITableViewCell.
But Xcode throws following Error:
Cannot assign value of type 'FirstViewController.Title' to type
'String?'
Maybe there is more wrong in my code, because i'm just a beginner at Swift/Xcode
I already tried this:
cell.textLabel?.text = course.title as? String
But i got warning message as follows:
Cast from 'FirstViewController.Title' to unrelated type 'String' always fails
This is my code sample:
var courses = [Course]()
let cell = "ItemCell"
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
struct Course: Codable {
let title: Title
enum CodingKeys: String, CodingKey {
case title
case links = "_links"
}
}
struct Links: Codable {
}
struct Title: Codable {
let rendered: String
}
fileprivate func fetchJSON() {
let urlString = "ExampleURL"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(Course.self, from: data)
self.tableView.reloadData()
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: "ItemCell")
let course = courses[indexPath.row]
cell.textLabel?.text = course.title as? String // Cast from 'FirstViewController.Title' to unrelated type 'String' always fails
return cell
}
I just want to get WordPress posts into a UITableView - UITableViewCell.
Maybe you can tell me if its the wrong way i tried it but i don't really know how i solve this problem
Thank you in advance
Assign the var before the reload
let res = try JSONDecoder().decode(Course.self, from: data)
courses.append(res)
DispatchQueue.main.async {
self.tableView.reloadData()
}
And set it to the string value
cell.textLabel?.text = course.title.rendered
courses = try JSONDecoder().decode([Course].self, from: data)
print(courses)

load large data from firestore to table view Swift

firestore to store about more than 500 information and I want to display it to table view. Basically, I have successfully display all the data in my cell, but the problem is, it takes more than 1 minute to load all data. While the data loaded, I cannot scroll the table view, unless all data finish load. How to enable scrolling while the data is still loading? If not possible, how to load first 20 data first, and will continue load if user is at the end of the cell? Here is some code that I have tried to
get data from firestore:
func getData () {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
let data = document.data()
let agencyPath = data["agensi"] as? String ?? ""
let title = data["title"] as? String ?? ""
let program = data["program"] as? String ?? ""
let perniagaan = data["perniagaan"] as? String ?? ""
let newMax = data["max"] as? Int
let agencyId = document.documentID
let query = Firestore.firestore().collection("Agensi")
let newQuery = query.whereField("name", isEqualTo: "\(agencyPath)")
newQuery.getDocuments()
{
(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)");
} else
{
for document in querySnapshot!.documents {
let data = document.data()
let logo = data["logo"] as? String ?? ""
//store to Struct
let newModel = DisplayModel(agency: title, agencyId: agencyId, programTag: program, perniagaanTag: perniagaan, max: newMax, agencyPath: agencyPath, logoUrl: logo, agencyTitle: agencyPath)
self.agencyList.append(newModel)
}
self.tableView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}
}
}
}
}
display data on cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData: DisplayModel
if searchController.searchBar.text != "" {
cellData = filteredData[indexPath.row]
} else {
cellData = agencyList[indexPath.row]
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? HomeTableViewCell
cell?.agencyName.text = cellData.agency
cell?.agencyImage.sd_setImage(with: URL(string: "\(cellData.logoUrl ?? "")"), placeholderImage: UIImage(named: "no_pic_image"))
return cell!
}
Action on last row of cell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if searchController.searchBar.text != "" {
let lastElement = filteredData.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
} else {
let lastElement = agencyList.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
}
}
I really have no idea what method I should do to load 20 data first and continue load at the end of cell row, if there is no solution, at least I could scroll the table view during the load session. Thank You, for your information, i just learn swift last month. Thank you for helping me.
You should definitly adopt the UITableViewDataSourcePrefetching protocol.
Check some blogs, like:
https://www.raywenderlich.com/187041/uitableview-infinite-scrolling-tutorial
and adopt it to pagination as described here:
https://firebase.google.com/docs/firestore/query-data/query-cursors

Firestore - Creating a copy of a collection

So I have a collection called "Drafts" which contains multiple documents each with an auto ID. Each document contains the fields "name" and "details". Each document is displayed in a tableViewCell under "nameLabel" and "detailsLabel". What I would like to do is when the user clicks on a button at the top of the screen of the First viewController, a copy of the collection "Drafts" is created and pasted under a new collection name called "Messages". This collection is then referencing the Second viewControllers tableViewCells just like on the First ViewController only this time its being referenced under the collection "Messages". Having done some research I have a vague inclination that the answer uses cloud functions to essentially create a copy of the collection and paste it with a new collection name. However being relatively new to coding and firebase, I have no idea how to do this and don't know if this is the correct solution. Please may someone help, any help is greatly appreciated!! Thanks!
First ViewController
func loadDrafts() {
let userRef = db.collection("Users").document(user!)
let draftsRef = userRef.collection("Drafts")
exercisesRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let details = data["details"] as? String ?? ""
let newDrafts = DraftMessages(name: name, details: details)
self.array.append(newDrafts)
}
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! DraftsCell
cell.nameLabel.text = array[indexPath.row].name
cell.detailsLabel.text = array[indexPath.row].details
return cell
}
#IBAction func goButton(_ sender: UIButton) {
\\ Add code here to create copy of previous collection "Drafts" and paste in new collection "Messages"
}
Second ViewController
func loadData() {
let userRef = db.collection("Users").document(user!)
userRef.collection("Messages").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let details = data["details"] as? String ?? ""
let newMessages = Messages(name: name, details: details)
self.array.append(newMessages)
}
self.tableView.reloadData()
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MessagesCell
cell.nameLabel.text = array[indexPath.row].name
cell.detailsLabel.text = array[indexPath.row].details
return cell
}
Here is my working solution. Many thanks to Franks for the help!
#IBAction func goButton(_ sender: UIButton) {
let userRef = db.collection("Users").document(user!)
let draftsRef = userRef.collection("Drafts")
draftsRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let batch = self.db.batch()
let docset = querySnapshot
let messagesRef = userRef.collection("Messages").document()
docset?.documents.forEach {_ in batch.setData(data, forDocument: messagesRef)}
batch.commit(completion: { (error) in
if let error = error {
print("\(error)")
} else {
print("success")
}
})
}
}
}
}
}
Edit for Vaibhav Jhaveri:
This function (hopefully) both duplicates the fetched documents data and the data inside of that documents subcollection. (I have not tested this though)
func duplicate() {
let userRef = db.collection("Users").document(userID)
let batch = self.db.batch()
let draftsRef = userRef.collection("Drafts")
draftsRef.getDocuments { (snapshot, err) in
if let err = err {
print(err.localizedDescription)
return
}
guard let snapshot = snapshot else { return }
snapshot.documents.forEach({ (document) in
let data = document.data()
let messageID = UUID().uuidString
let messagesRef = userRef.collection("Messages").document(messageID)
batch.setData(data, forDocument: messagesRef, merge: true)
let yourSubCollectionRef = draftsRef.document(document.documentID).collection("yourSubCollection")
yourSubCollectionRef.getDocuments(completion: { (subSnapshot, subErr) in
if let subErr = subErr {
print(subErr.localizedDescription)
return
}
guard let subSnapshot = subSnapshot else { return }
subSnapshot.documents.forEach({ (subDocument) in
let subData = subDocument.data()
let subDocID = UUID().uuidString
let yourNewSubCollectionRef = userRef.collection("Messages").document(messageID).collection("yourSubCollection").document(subDocID)
batch.setData(subData, forDocument: yourNewSubCollectionRef, merge: true)
})
})
})
batch.commit()
}
}