I am setting up my swift code to use MySQL database. Up until this point I have only used Firebase/Firestore. I have installed the OHMySQL library via CocoaPods. I am confused as to how the Object Mapping works. I have added an example of how I did it using Firestore.
func fetchProductData(){
let db = Firestore.firestore()
db.collection("Products").getDocuments { (snap, err) in
guard let productData = snap else{return}
self.products = productData.documents.compactMap({ (doc) -> Product? in
let id = doc.documentID
let name = doc.get("product_name") as? String ?? "No product name"
let image = doc.get("product_image") as? String ?? "No product image"
let details = doc.get("product_details") as? String ?? "No product details"
let website = doc.get("product_website") as? String ?? "No product website"
let stock = doc.get("product_stock") as! Int
let uid = doc.get("product_uid") as? String ?? "No uid"
return Product(id: id, product_name: name, product_image: image, product_details: details, product_website: website, product_stock: stock, product_uid: uid)
})
self.filteredProduct = self.products
}
}
I believe that as I am using Swift, I cannot use fundamental number types (Int, Double), only NSNumber.
What would this mapping be like using a MySQL database?
This is an example of how to get the product name data:
func fetchProductData() {
let url = URL(string: "INSERT YOUR URL HERE")!
//2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let todoData = data {
//3.
let decodedData = try JSONDecoder().decode([Product].self, from: todoData)
DispatchQueue.main.async {
self.products = decodedData
self.filteredProduct = decodedData
}
//Fetching name of product
decodedData.forEach { course in print(course.product_name)}
} else {
print("No Data")
}
} catch {
print(error)
}
}
.resume()
}
Related
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)
}
I want to load all data from firebase, then show the data to the table view. But now, I can't show all the data to the table view. It is because call the finishLoading(realm) method is faster than the for loop get all the data. How can I do some show all data when for loop is finish in swift. I have to use the Closure, however the second of the loop is later than this "self.finishLoading(realm: realm)"
I have to try to add the DispatchGroup(), however, the leave() when having an error of EXC_BAD_INSTRUCTION. Can I put the leave() in the closure? How can I fix it?
func loopAllProduct(userId: String, finishLoadWhenErr:Bool, storedClosure: #escaping (DocumentSnapshot) -> Void){
let storage = Storage.storage()
let db = Firestore.firestore()
let userDocRef = db.collection("Users").document(userId).collection("Product")
userDocRef.getDocuments{(document, error) in
if let err = error {
print("Error getting documents: \(err)")
} else {
for document in document!.documents {
storedClosure(document)
}
}
}
}
func downloadData() {
let startTime = Date()
while updating {
let diffTime = Date(timeIntervalSinceReferenceDate: startTime.timeIntervalSinceReferenceDate)
if (diffTime.timeIntervalSinceNow < -5){
self.stopAnimating()
self.refreshControl?.endRefreshing()
print("Update Timeout")
return
}
}
updating = true
let storage = Storage.storage()
let db = Firestore.firestore()
let productLoading = NSMutableArray()
let realm = try! Realm()
print("all posts")
let group = DispatchGroup()
let addPosts: (DocumentSnapshot)->Void = {(document) in
try! realm.write {
if let resuls = self.realmResults {
realm.delete(resuls);
}
}
let product = Product()
product.id = document.documentID
product.userID = document.data()?["UserID"] as? String
product.userName = document.data()?["UserName"] as? String
product.descrition = document.data()?["Descrition"] as? String
product.postTime = document.data()?["PostTime"] as? Date
product.price = document.data()?["Price"] as? Double ?? 0.0
product.stat = (document.data()?["stat"] as? Int)!
product.productName = document.data()?["ProductName"] as? String
let productId = document.documentID
productLoading.add(productId)
try! realm.write {
realm.add(product)
}
group.leave()
}
let userDocRef = db.collection("Users")
userDocRef.getDocuments{(document, error) in
for document in document!.documents {
group.enter()
self.loopAllProduct(userId:document.documentID , finishLoadWhenErr: true, storedClosure: addPosts)
}
}
group.notify(queue: DispatchQueue.main) {
self.finishLoading(realm: realm)
}
}
I have trouble retrieving a UIImage from Firebase Storage, the child path seems to be correct, though the image does not get "downloaded" to be displayed. The part about the Firebase Database is working fine, hence retrieving data, whereas the Storage one is now. Code and Firebase path below
I cannot understand whether the problem is in the fact that I nested the function into the .observeSingleEvent of the Database retrieving function or not.
gs://xxxyyy-xxxyyy.appspot.com/images/QhRmIcbF7AOWjZ3nrjFd7TOekrA3/FirstImage.jpg
var cells : [Cella] = []
var imageReference: StorageReference {
return Storage.storage().reference().child("images")
}
var databaseReference: DatabaseReference {
return Database.database().reference()
}
func getDataFromFirebase() -> [Cella]{
let queryRef = databaseReference.queryLimited(toLast: 1)
var appCells : [Cella] = []
queryRef.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
var userPhoto : UIImage?
let userSnap = snap as! DataSnapshot
let customerUid = userSnap.key
let userDict = userSnap.value as! [String:AnyObject]
let description = userDict["description"] as! String
let title = userDict["title"] as! String
print(title)
print(String(customerUid))
print(description)
self.descriptionsArray[String(customerUid)] = description
self.titlesArray[String(customerUid)] = title
//error is here BECAUSE it can't retrive the image to be dispalyed. Title and description are fine
self.imageReference.child(String(customerUid)).child("FirstImage.jpg").getData(maxSize: 10*1024*1024, completion: { (data, error) in
if error != nil {
print("\(String(describing: error?.localizedDescription))")
}
else {userPhoto = UIImage(data: data!)}
})
let newCella = Cella(image: userPhoto!, title: title, bodyMessage: description)
appCells.append(newCella)
}
})
return appCells
}
------ UPDATE ------
As suggested I changed to using firebase Firestore and saving there the download URL as well as the other information. Still though, I cannot seem to get the image downloading. New code below.
This is the data retrieved by document.data() :
xxx.yyy#gmail.com => ["userID": QhRmIcbF7AOWjZ3nrjFd7TOekrA3, "userDescription": Route66, "userImageUrl": https://firebasestorage.googleapis.com/v0/b/shardana-61183.appspot.com/o/images%2FQhRmIcbF7AOWjZ3nrjFd7TOekrA3%2FFirstImage.jpg?alt=media&token=dea541bf-d598-414e-b4ed-a917541598d5, "userTitle": Sample]
firestoreUsersDatabase.getDocuments { (querySnapshot, error) in
if let error = error {
print("Error getting the documents: \(error)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
let data = document.data()
let imageUrl = data["userImageUrl"] as! String
let title = data["userTitle"] as! String
let description = data["userDescription"] as! String
let urlDownloadReference = self.imageReference.reference(forURL: imageUrl)
urlDownloadReference.getData(maxSize: 10*2014*2014, completion: { (data, error) in
if error != nil {
print("An error occurred: \(String(describing: error?.localizedDescription))")
} else {
guard let imageDownloaded = UIImage(data: data!) else {print("Image url returned nil value ERROR"); return}
let newCell = Cella(image: imageDownloaded, title: title , bodyMessage: description )
print("NEW CELL: Image \(newCell.image)")
appCells.append(newCell)
}
})
}
}
}
yes, I think you're logic needs review. You need to store on your Firestore all the users data, including all the references to needed images. On the other hand, Firebase Storage, which is a different service within Firebase will save the images an will give you download links, but it uses a different logic than Firestore.
See the following example for clarification on what I mean:
https://firebase.google.com/docs/storage/web/download-files
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
I'm running into a problem when I try to make a request to YQL for stock data, when the symbol (newCompanyStockSymbol) to look up is user-entered. I fetch the stocks in this function:
func handleSave() {
// Fetch stock price from symbol provided by user for new company
guard let newCompanyStockSymbol = stockTextField.text else {
print("error getting text from field")
return
}
var newCompanyStockPrice = ""
let url = URL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20symbol%2C%20Ask%2C%20YearHigh%2C%20YearLow%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22\(newCompanyStockSymbol)%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
let json = JSON(data: data!)
if let quotes = json["query"]["results"]["quote"].array {
for quote in quotes {
let ask = quote["Ask"].stringValue
newCompanyStockPrice = ask
}
}
print("new company json: \(json)")
}
guard let newCompanyName = self.nameTextField.text else {
print("error getting text from field")
return
}
guard let newCompanyLogo = self.logoTextField.text else {
print("error getting text from field")
return
}
print("2: The new commpany stock price is: \(newCompanyStockPrice)")
// Call save function in view controller to save new company to core data
self.viewController?.save(name: newCompanyName, logo: newCompanyLogo, stockPrice: newCompanyStockPrice)
self.viewController?.tableView.reloadData()
}
task.resume()
// Present reloaded view controller with new company added
let cc = UINavigationController()
let companyController = CompanyController()
viewController = companyController
cc.viewControllers = [companyController]
present(cc, animated: true, completion: nil)
}
And I use string interpolation to insert \(newCompanyStockSymbol) into the request URL at the appropriate place. However I get a crash and error on that line because it's returning nil, I expect because it's using the URL with \(newCompanyStockSymbol) in there verbatim, instead of actually inserting the value.
Is there another way to do this?
EDIT
And the save function in view controller that's called from handleSave() above if it's helpful:
func save(name: String, logo: String, stockPrice: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
let entity =
NSEntityDescription.entity(forEntityName: "Company",
in: managedContext)!
let company = NSManagedObject(entity: entity,
insertInto: managedContext)
company.setValue(stockPrice, forKey: "stockPrice")
company.setValue(name, forKey: "name")
company.setValue(logo, forKey: "logo")
do {
try managedContext.save()
companies.append(company)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
tableView.reloadData()
}
Supposing you entered AAPL in your stockTextField, using simply:
let newCompanyStockSymbol = stockTextField.text
results in newCompanyStockSymbol being:
Optional("AAPL")
which is not what you want in your URL string. The critical section ends up like this:
(%22Optional("AAPL")%22)
Instead, use guard to get the value from the text field:
guard let newCompanyStockSymbol = stockTextField.text else {
// handle the error how you see fit
print("error getting text from field")
return
}
Now your URL should be parsed correctly.
--- Additional info ---
I'm not entirely sure of the rules on 'continued conversation' around here, but hopefully editing this will be acceptable... anyway...
Make sure you are following this flow:
func handleSave() {
let newCompanyName = nameTextField.text
let newCompanyStockSymbol = stockTextField.text
let newCompanyLogo = logoTextField.text
var newCompanyStockPrice = ""
// Fetch stock price from symbol provided by user for new company
let url = URL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20symbol%2C%20Ask%2C%20YearHigh%2C%20YearLow%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22\(newCompanyStockSymbol)%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
} else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
let json = JSON(data: data!)
if let quotes = json["query"]["results"]["quote"].array {
for quote in quotes {
let ask = quote["Ask"].stringValue
newCompanyStockPrice = ask
// task completed, we've parsed the return data,
// so NOW we can finish the save process and
// update the UI
viewController?.save(name: newCompanyName!, logo: newCompanyLogo!, stockPrice: newCompanyStockPrice)
}
}
}
}
task.resume()
}
I'm not testing this, so it might need a tweak, and your .save() function may need to be forced onto the main thread (since it's doing UI updates). But maybe that's a little more clear.