How to display Firestore data into mobile app using Swift? - ios

I would like to display data from the Firestore database into my mobile app using the Swift TableView however I am stuck with the coding.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class BookListViewController: UIViewController {
var bookCollectionRef: CollectionReference!
var books = [Books]()
var id: String
var bookAuthor: String
var bookSummary: String
var bookTitle: String
override func viewDidLoad() {
super.viewDidLoad()
bookCollectionRef = Firestore.firestore().collection("bookData")
bookCollectionRef.getDocuments { [weak self] (snapshot, error ) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
} else {
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
_ = data[self?.bookAuthor ?? <#default value#>] as? String ?? "Anonymous"
_ = data[self?.bookSummary] as? String ?? ""
_ = data[self?.bookTitle] as? String ?? ""
The collection that I wanted to display is the bookData:

I think you forget to append your books array with fetched data and don't forget to reload tableview.
Try this code:
else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let author = data["bookAuthor"] as? String ?? ""
let title = data["bookTitle"] as? String ?? ""
let summary = data["bookSummary"] as? String ?? ""
let newBook = (bookAuthor:author,booktitle:title,bookSummary:summary)
self.books.append(newBook)
}
self.tableview.reloadData()

Related

How to convert document to a custom object in Swift 5?

I've been trying to convert the document retrieved from the Firebase's Cloud Firestore to a custom object in Swift 5. I'm following the documentation:
https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
However, Xcode shows me the error Value of type 'NSObject' has no member 'data' for the line try $0.data(as: JStoreUser.self). I've defined the struct as Codable.
The code:
func getJStoreUserFromDB() {
db = Firestore.firestore()
let user = Auth.auth().currentUser
db.collection("users").document((user?.email)!).getDocument() {
(document, error) in
let result = Result {
try document.flatMap {
try $0.data(as: JStoreUser.self)
}
}
}
}
The user struct:
public struct JStoreUser: Codable {
let fullName: String
let whatsApp: Bool
let phoneNumber: String
let email: String
let creationDate: Date?
}
The screenshot:
Does anyone know how to resolve this?
After contacting the firebase team, I found the solution I was looking for. It turns out I have to do import FirebaseFirestoreSwift explicitly instead of just doing import Firebase. The error will disappear after this. (And of course you'll need to add the pod to your podfile first:D)
You can do it as shown below:-
First create model class:-
import FirebaseFirestore
import Firebase
//#Mark:- Users model
struct CommentResponseModel {
var createdAt : Date?
var commentDescription : String?
var documentId : String?
var dictionary : [String:Any] {
return [
"createdAt": createdAt ?? "",
"commentDescription": commentDescription ?? ""
]
}
init(snapshot: QueryDocumentSnapshot) {
documentId = snapshot.documentID
var snapshotValue = snapshot.data()
createdAt = snapshotValue["createdAt"] as? Date
commentDescription = snapshotValue["commentDescription"] as? String
}
}
Then you can convert firestore document into custom object as shown below:-
func getJStoreUserFromDB() {
db = Firestore.firestore()
let user = Auth.auth().currentUser
db.collection("users").document((user?.email)!).getDocument() { (document, error) in
// Convert firestore document your custom object
let commentItem = CommentResponseModel(snapshot: document)
}
}
You need to initialize your struct and then you can extend the QueryDocumentSnapshot and QuerySnapshot like:
extension QueryDocumentSnapshot {
func toObject<T: Decodable>() throws -> T {
let jsonData = try JSONSerialization.data(withJSONObject: data(), options: [])
let object = try JSONDecoder().decode(T.self, from: jsonData)
return object
}
}
extension QuerySnapshot {
func toObject<T: Decodable>() throws -> [T] {
let objects: [T] = try documents.map({ try $0.toObject() })
return objects
}
}
Then, try to call the Firestore db by:
db.collection("users").document((user?.email)!).getDocument() { (document, error) in
guard error == nil else { return }
guard let commentItem: [CommentResponseModel] = try? document.toObject() else { return }
// then continue with your code
}
In the past, I had some issues though importing FirebaseFirestore with the package manager in my project.
So I explain about the access to FirebaseFirestore in swift.
SnapshotListener
import Foundation
import FirebaseFirestore
class BooksViewModel: ObservableObject {
#Published var books = [Book]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("books").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.books = documents.map { queryDocumentSnapshot -> Book in
let data = queryDocumentSnapshot.data()
let title = data["title"] as? String ?? ""
let author = data["author"] as? String ?? ""
let numberOfPages = data["pages"] as? Int ?? 0
return Book(id: .init(), title: title, author: author, numberOfPages: numberOfPages)
}
}
}
}
using uid and getDocument function
Firestore.firestore().collection("users").document(uid).getDocument { snapshot, error in
if let error = error {
self.errorMessage = "Failed to fetch current user: \(error)"
print("Failed to fetch current user:", error)
return
}
guard let data = snapshot?.data() else {
self.errorMessage = "No data found"
return
}
let uid = data["uid"] as? String ?? ""
let email = data["email"] as? String ?? ""
let profileImageUrl = data["profileImageUrl"] as? String ?? ""
self.chatUser = ChatUser(uid: uid, email: email, profileImageUrl: profileImageUrl)
}

AddSnapShotListener is repeating the document reading in one instance

When I am using addSnapshotListener for realtime updates, the documents are repeated which should not be the case, but when using getDocuments() the documents are repeated once only, I need to use addSnaphotListener but not want to duplicate the document reading, please assist where I am wrong in using snapshot listener.
I am using Firestore database in Swift iOS. Below is the code I am using
Code with addSnapShotListener():
func getComments() {
//print(postId + "received")
let commentsRef = Firestore.firestore().collection("posts").document(postId).collection("comments")
commentsRef.addSnapshotListener { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
// self.length = snapshot.count
let data = document.data()
let username = data["comment_author_username"] as? String ?? ""
let comment = data["comment_author_comment"] as? String ?? ""
let spinnerC = data["comment_author_spinnerC"] as? String ?? ""
let fullname = data["comment_author_fullname"] as? String ?? ""
let email = data["comment_author_email"] as? String ?? ""
let commentUserImageUrl = data["comment_user_image"] as? String ?? ""
let commentuser_id = data["comment_author_id"] as? String ?? ""
self.checkl1value = data["l1"] as? Bool
let newComment = Comment(_documentId: document.documentID, _commentAuthorUsername: username, _commentAuthorFullName: fullname, _commentAuthorComment: comment, _commentUserImage: commentUserImageUrl, _commentAuthorSpinnerC: spinnerC, _commentAuthorId:commentuser_id, _checkl1value: self.checkl1value)
self.comments.append(newComment)
// print(self.length!)
}
self.tableView.reloadData()
}
}
}
}
Code With getDocuments():
func getComments() {
//print(postId + "received")
let commentsRef = Firestore.firestore().collection("posts").document(postId).collection("comments")
commentsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
// self.length = snapshot.count
let data = document.data()
let username = data["comment_author_username"] as? String ?? ""
let comment = data["comment_author_comment"] as? String ?? ""
let spinnerC = data["comment_author_spinnerC"] as? String ?? ""
let fullname = data["comment_author_fullname"] as? String ?? ""
let email = data["comment_author_email"] as? String ?? ""
let commentUserImageUrl = data["comment_user_image"] as? String ?? ""
let commentuser_id = data["comment_author_id"] as? String ?? ""
self.checkl1value = data["l1"] as? Bool
let newComment = Comment(_documentId: document.documentID, _commentAuthorUsername: username, _commentAuthorFullName: fullname, _commentAuthorComment: comment, _commentUserImage: commentUserImageUrl, _commentAuthorSpinnerC: spinnerC, _commentAuthorId:commentuser_id, _checkl1value: self.checkl1value)
self.comments.append(newComment)
// print(self.length!)
}
self.tableView.reloadData()
}
}
}
}
You're probably looking to only handle the changes between the snapshots. To do that you'll want to loop over instead of, as shown in the documentation on viewing changes between snapshots:
db.collection("cities").whereField("state", isEqualTo: "CA")
.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
print("New city: \(diff.document.data())")
}
if (diff.type == .modified) {
print("Modified city: \(diff.document.data())")
}
if (diff.type == .removed) {
print("Removed city: \(diff.document.data())")
}
}
}
Initially your listener will get called with diff.type == .added for each existing document, and then when there are changes it'll get called with the right mix of types.

Swift How to fetch user with userID

I´m new to Swift or rather the whole programming world and I´m trying to learn how to program in Swift.
I got a table view with cells (VC1) which contain different information provided by a user. I want to click on a cell to get to a new VC by segue. This new VC should display the user information of the user who posted the information. I´m passing the userID from VC1 to VC2 by segue.
In VC2 I want to download the user information using the userID.
I´m using a UserModel
import Foundation
class UserModel {
var username: String?
var email: String?
var profilImageUrl: String
var birthdayDate: String?
var gender: String?
init(dictionary: [String: Any]) {
username = dictionary["username"] as? String
email = dictionary["email"] as? String
profilImageUrl = dictionary["profilImageURL"] as? String ?? ""
birthdayDate = dictionary["Geburtsdatum"] as? String
gender = dictionary["gender"] as? String
}
}
Passing the userID VC1 -> VC2 works, I checked it using the print command
My Code from VC2:
//MARK: - Outlets
#IBOutlet weak var nameLabel: UILabel!
//MARK: Var/ Let
var event: EventModel?
var users = [UserModel]()
var user: UserModel?{
didSet {
guard let _username = user?.username else {return}
nameLabel.text = _username
}
}
func loadUserDeatails(){
guard let userID = event?.uid else {return}
fetchUser(uid: userID)
}
func fetchUser(uid: String) {
let userRef = Database.database().reference().child("users").child(uid)
userRef.observeSingleEvent(of: .value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else {return}
let newUser = UserModel(dictionary: dic)
self.users.append(newUser)
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadUserDeatails()
print(users)
}
the nameLabel is empty as well as the array(users) which I tried to print in ViewDidLoad.
Var "dic" is containing the user details I downloaded with "guard let userID = event?.uid" so I guess that should be right.
Hopefully someone can help me :)
I'm not sure what event is doing, but one approach is:
In VC1:
In prepareforsegue, once you get the reference of VC2,
let vc2 = segue.destination as! VC2
vc2.userId = "<USER_ID>" // Pass userId to VC2
In VC2: didSet of var userId would do the fetch, then assign username to nameLabel.text directly.
var userId: String? {
didSet {
// Do the fetch user details here
guard let userId = userId else {return}
let userRef = Database.database().reference().child("users").child(userID)
userRef.observeSingleEvent(of: .value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else {return}
let newUser = UserModel(dictionary: dic)
// Once your get user detail data, set the label text
nameLabel.text = newUser["username"] ?? ""
}
}
}
Try the following, it might work.
var user: UserModel?{
didSet {
guard let _username = user?.username else {return}
guard let userID = event?.uid else {return}
let userRef = Database.database().reference().child("users").child(userID)
userRef.observeSingleEvent(of: .value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else {return}
let newUser = UserModel(dictionary: dic)
nameLabel.text = newUser["username"] ?? ""
}
}
}

retrieve values from firestore's struct in swift

I'm querying some data from my firestore,and I put it in my Usersdata,
but I dont know how to get my values from Usersdata.
Please help me to query my data!
This is my struct base on Firestroe example
struct Usersdata {
let uid:String?
let facebook:String?
let google:String?
let name:String?
let age:Int?
let birthday:String?
let smokeage:Int?
let smokeaddiction:Int?
let smokebrand:String?
let gold:Int?
let score:Int?
let fish:Int?
let shit:Int?
let userimage:String?
init?(dictionary: [String: Any]) {
guard let uid = dictionary["uid"] as? String else { return nil }
self.uid = uid
self.facebook = dictionary["facebook"] as? String
self.google = dictionary["google"] as? String
self.name = dictionary["name"] as? String
self.age = dictionary["age"] as? Int
self.birthday = dictionary["birthday"] as? String
self.smokeage = dictionary["smokeage"] as? Int
self.smokeaddiction = dictionary["smokeaddiction"] as? Int
self.smokebrand = dictionary["smokebrand"] as? String
self.gold = dictionary["gold"] as? Int
self.score = dictionary["score"] as? Int
self.fish = dictionary["fish"] as? Int
self.shit = dictionary["shit"] as? Int
self.userimage = dictionary["userimage"] as? String
}
}
this is my function to query data from firebase
func test(schema:String , collection:String , document : String){
let queryRef = db.collection("Users").document(userID).collection(collection).document(document)
queryRef.getDocument { (document, error) in
if let user = document.flatMap({
$0.data().flatMap({ (data) in
return Usersdata(dictionary: data)
})
}) {
print("Success \(user)")
} else {
print("Document does not exist")
}
}
}
I think you are asking how to work with a structure with Firebase data. Here's a solution that will read in a known user, populate a structure with that data and then print the uid and name.
Assume a stucture
Users
uid_0
name: "Henry"
and then a structure to hold that data
struct Usersdata {
let uid:String?
let user_name:String?
init(aDoc: DocumentSnapshot) {
self.uid = aDoc.documentID
self.user_name = aDoc.get("name") as? String ?? ""
}
}
and a function to read that user, populate the struct and print out data from the struct
func readAUser() {
let docRef = self.db.collection("Users").document("uid_0")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let aUser = Usersdata(aDoc: document)
print(aUser.uid, aUser.user_name)
} else {
print("Document does not exist")
}
}
}
and the output
uid_0 Henry

core data fetch result not append to custom class in swift

I am fetching data from the coredata it is working fine but I am append coredata values to custom class, when show fetch data it showing like this
name Optional(Rahul Gandhi)desc Optional(Indian politician) profileimage Optional(https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Rahul_Gandhi_%28headshot%29.jpg/37px-Rahul_Gandhi_%28headshot%29.jpg)
name Optional(Rahul Raj)desc Optional(Indian composer) profileimage Optional(https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Shiva_Temple%2C_Dhoni%2C_Palakkad.jpg/37px-Shiva_Temple%2C_Dhoni%2C_Palakkad.jpg)
but after append the core data to custom class I'm showing that data in tableview but it showing empty
class ModelData
{
var name : String?
var Description:String?
var ProfileImage:String?
init(name:String?,Desctiption:String,ProfileImage:String) {
self.name = name
self.Description = Desctiption
self.ProfileImage = ProfileImage
}
var modeldata = [ModelData]()
func corefetch()
{
do{
let fetchRequest : NSFetchRequest<Val> = Val.fetchRequest()
let Data = try context.fetch(fetchRequest)
if Data.count > 0
{
for actor in Data as [Val]
{
let nameval = actor.value(forKey: "title")
let profileImage = actor.value(forKey: "profile")
let descrption = actor.value(forKey: "desc")
print("name \(String(describing: nameval))desc \(String(describing: descrption)) profileimage \(String(describing: profileImage))")
self.modeldata.append(ModelData.init(name: nameval as? String, Desctiption: descrption as! String, ProfileImage: profileImage as! String))
}
}
}catch { print(error) }
}
I tried many ways I did't find any solution how to append the coredata values to custom class
Dont forget to save data
do {
try context.save()
} catch {
print("Failed saving")
}

Resources