Struggling to pass data to UI - Swift - swift

Good day, I'm trying to pass data to my Profile UI View.
This is my Customer Class:
class Customer {
// Creating a customer
let name: String
let surname: String
let contactNo: String
let email: String
init(name: String,surname: String,contactNo: String,email: String) {
self.name = name
self.surname = surname
self.contactNo = contactNo
self.email = email
}
}
This is my code whereby I try to parse data from Firestore to display a customers details:
class ProfileCus: UIViewController {
// Labels to display data
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var surnameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
#IBOutlet weak var contactLabel: UILabel!
// Reference to customer collection in Firestore
private var customerRefCollection: CollectionReference!
// Customer Object
private var customer: Customer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
customerRefCollection = Firestore.firestore().collection("customers")
nameLabel.text = customer?.name
surnameLabel.text = customer?.surname
emailLabel.text = customer?.email
contactLabel.text = customer?.contactNo
}
// This function notifies the view controller that the view is about to load so it is best to acquire the data in here before the view loads so that it will be ready to display.
override func viewWillAppear(_ animated: Bool) {
// Get the current user ID
let userID = Auth.auth().currentUser?.uid
// Locate the user information on Firestore
customerRefCollection.document(userID!).getDocument { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
// Ensure that if there's nothing in the document that the function returns
guard let snap = snapshot else {return}
// Parse the data to the customer model
let data = snap.data()
let name = data?["name"] as? String ?? ""
let surname = data?["surname"] as? String ?? ""
let email = data?["email"] as? String ?? ""
let contact = data?["contact no"] as? String ?? ""
// Create the customer and pass it to the global variable
let cus = Customer(name: name, surname: surname, contactNo: contact, email: email)
self.customer = cus
}
}
}
}
Everything on the Firestore side is working fine and I am able to read/retrieve data but I'm struggling to pass the data to my UI via my Customer Class. Using print statements it seems as if the customer object is nil.
Output once the code runs

func getDataFromFirebase(completion:#escaping() -> ()){
let userID = Auth.auth().currentUser?.uid
// Locate the user information on Firestore
customerRefCollection.document(userID!).getDocument { (snapshot, error) in
if let err = error {
debugPrint("Error fetching documents: \(err)")
}
else {
// Ensure that if there's nothing in the document that the function returns
guard let snap = snapshot else {return}
// Parse the data to the customer model
let data = snap.data()
let name = data?["name"] as? String ?? ""
let surname = data?["surname"] as? String ?? ""
let email = data?["email"] as? String ?? ""
let contact = data?["contact no"] as? String ?? ""
// Create the customer and pass it to the global variable
let cus = Customer(name: name, surname: surname, contactNo: contact, email: email)
self.customer = cus
}
completion()
}
}
getDataFromFirebase{
customerRefCollection = Firestore.firestore().collection("customers")
nameLabel.text = customer?.name
surnameLabel.text = customer?.surname
emailLabel.text = customer?.email
contactLabel.text = customer?.contactNo
}
So what you're basically doing here is you're first getting the data from the firebase and only after this work is done, hence the completion block you will be setting your data to the view. You can call the method simply in viewDidLoad.

Related

How Can I Use Two URLs Asynchronously to Parse JSON data

So I am using a URL in the bolded text to parse JSON data retrieved remotely from that URL. My issue is that I want to parse data remotely AND asynchronously from TWO URLs not just one. The following code works great for 1 URL but I haven't the slightest idea how to do the same thing for 2. I am fairly new to Swift to any tips or pointers would be appreciated.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var customerNameLabel: UILabel!
#IBOutlet weak var cardNumberLabel: UILabel!
#IBOutlet weak var dateNTimeLabel: UILabel!
#IBOutlet weak var amountLabel: UILabel!
var customers = [Customer]()
var currentCustomerIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Retrieve JSON data from a remote server
let config = URLSessionConfiguration.default
// Create a session
let session = URLSession(configuration: config)
// Validate the URL to ensure that it is not a broken link
if let validURL = URL(string: "**THISISMYJSONURLHERE(removedforsecurity)**") {
//Create a task that will download whatever is found at validURL as a Data object
let task = session.dataTask(with: validURL, completionHandler: { (data, response, error) in
// If there is an error, we are going to bail out of this entire method (hence return)
if let error = error {
print("Data task failed with error: " + error.localizedDescription)
return
}
// If we get here that means we have received the info at the URL as a Data Object nd we can now ue it
print("Success")
//Check the response status
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let validData = data
else {print("JSON Object Creation Failed"); return}
do {
let jsonObj = try JSONSerialization.jsonObject(with: validData, options: .mutableContainers) as? [Any]
// Call our Parse method
self.ParseData(jsonObject: jsonObj)
self.displayData()
}
catch {
print(error.localizedDescription)
}
task.resume()
}
}
func ParseData(jsonObject: [Any]?) {
guard let json = jsonObject
else { print("Parse failed to unwrap the optional."); return }
for firstLevelItems in json {
guard let object = firstLevelItems as? [String: Any],
let fname = object["first_name"] as? String,
let lname = object["last_name"] as? String,
let fullName = fname + " " + lname as? String,
let customerNumber = object["customer_number"] as? Int,
let purchase = object["purchase"] as? [String: Any],
let time = purchase["time"] as? String,
let date = purchase["date"] as? String,
let amount = purchase["amount"] as? String
else { continue }
// See Note: Nested Functions
func addTransaction(_customer: Customer) {
if let cardNumber = purchase["card_number"] as? String? {
_customer.transactions.append(Transaction(firstName: fname, lastName: lname, time: time, date: date, amount: amount, cardNumber: cardNumber))
}
else {
_customer.transactions.append(Transaction(firstName: fname, lastName: lname, time: time, date: date, amount: amount))
}
}
let filteredCustomers = customers.filter({ (customer) -> Bool in
return customer.transactions[currentCustomerIndex].customerName == fullName
})
if filteredCustomers.count == 0 {
customers.append(Customer(customerNumber: customerNumber))
//Forced unwrapping here is ok because we know for a fact that customers wont be empty
addTransaction(_customer: customers.last!)
}
// If filtered array.count is 1 then that means we already have a customer object for this number
// In that case we just want to modify the existing customer object instead of creating a new one
else if filteredCustomers.count == 1 {
// filteredCustomer[0].customerNote = "This has been counted and Modified"
addTransaction(_customer: filteredCustomers[0])
}
else {
//See Note: Assertion
// Assertion Failure so that as we are building if this ever happens we know we have messed up
assertionFailure("No customers should exist twice in our customers array")
}
// print("Customer Number: \(customerNumber) has \(filteredCustomers.count) Orccurance in Customer's Array")
}
}
func displayData() {
DispatchQueue.main.async {
self.customerNameLabel.text = self.customers[self.currentCustomerIndex].customerName
self.cardNumberLabel.text = self.customers[self.currentCustomerIndex].cardNum
self.dateNTimeLabel.text = self.customers[self.currentCustomerIndex].dateNTime
self.amountLabel.text = "$" + self.customers[self.currentCustomerIndex].customerAmount.description
}
}
#IBAction func changeCustomer(_ sender: UIButton) {
currentCustomerIndex += sender.tag
if currentCustomerIndex < 0 {
currentCustomerIndex = customers.count - 1
}
else if currentCustomerIndex >= customers.count {
currentCustomerIndex = 0
}
displayData()
}
}

fetch the last document from Firestore collection

I have a tableViewController that fetch document from Firestore collection which called "paperHole" and i have refreshControl which is a UIrefreshControl that refresh the tableView but in this refreshControl action i want to fetch the last document only that recorded in the Firestore . what i mean is if the collection "paperHole" have 3 documents which is ( A, B, C ) and the last document recorded in this collection is C , i want to fetch the document C only .
i tried with limit(toLast: 1) but it didn't work for me
import UIKit
import FirebaseFirestore
import Firebase
import FirebaseAuth
import UserNotifications
class OrderTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var order5: UITableView!
struct GlobalVariable{
static var myString = String()
}
var db: Firestore!
var street = [String]()
var firstName = [String]()
var lastName = [String]()
var blockNumber = [String]()
var phone = [String]()
var reciept = [String]()
var houseNumber = [String]()
var price = [String]()
var amount = [String]()
var block = [String]()
var Area = [String]()
var names = [String]()
var refreshControl = UIRefreshControl()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.attributedTitle = NSAttributedString(string: "")
refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
order5.addSubview(refreshControl)
order5.separatorStyle = .none
order5.dataSource = self
order5.delegate = self
db = Firestore.firestore()
}
#objc func refresh(_ sender: AnyObject) {
loadData1Refresh()
}
func loadData1Refresh() {
Firestore.firestore().collection("paperHole").limit(toLast: 1).getDocuments() {
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
var count = 0
for document in querySnapshot!.documents {
count += 1
print("\(document.documentID) => \(document.data())");
self.firstName.append(document.get("firstname") as? String ?? "")
self.lastName.append(document.get("lastname") as? String ?? "")
self.street.append(document.get("street") as? String ?? "")
self.blockNumber.append(document.get("blockNumber") as? String ?? "")
self.Area.append(document.get("area") as? String ?? "")
self.phone.append(document.get("phone") as? String ?? "")
self.reciept.append(document.get("reciept") as? String ?? "")
self.houseNumber.append(document.get("houseNumber") as? String ?? "")
self.price.append(document.get("total price") as? String ?? "")
self.amount.append(document.get("amount") as? String ?? "")
}
}
self.order5.reloadData()
}
}
I believe that using limit won't work unless you specify an order too.
Firstly, you'll need to attach a date to your paperHole object. As per the Firebase Docs:
Important: Unlike "push IDs" in the Firebase Realtime Database, Cloud Firestore auto-generated IDs do not provide any automatic ordering. If you want to be able to order your documents by creation date, you should store a timestamp as a field in the documents.
Then you can query your data using:
Firestore.firestore().collection("paperHole").order(by: "date", descending: true).limit(to: 1)

Why is my Firestore data not displaying as table view?

I am currently working on a project with Xcode (User Interface: Storyboard) and Google Firebase. I cannot get my app to display the information like this:
Desired output
The way I got this picture was by starting a test project selecting SwiftUI as my User Interface, and only having ONE single view controller. In my app a user will only arrive to this page after they have logged in and click a button that takes them to the table view.
My app currently prints the result at the bottom of Xcode:
Current output
import UIKit
import FirebaseFirestore
class AssetViewController: UIViewController {
#IBOutlet weak var assetList: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let db = Firestore.firestore()
db.collection("assets").getDocuments { (snapshot, error) in
if error != nil {
print("error")
} else {
for document in (snapshot?.documents)! {
if let brand = document.data() ["brand"] as? String {
if let description = document.data() ["description"] as? String {
if let date = document.data() ["date"] as? String {
if let cost = document.data() ["cost"] as? String {
if let serial = document.data() ["serial"] as? String {
if let warranty = document.data() ["warranty"] as? String {
if let warranty_cost = document.data() ["warranty_cost"] as? String {
print("\(document.documentID) => \(document.data())") }
}
}
}
}
}
}
}
}
}
}
}
I have the following class:
import Foundation
import FirebaseFirestore
class AssetsViewModel: ObservableObject {
#Published var assets = [Asset] ()
private var db = Firestore.firestore()
func fetchData() {
db.collection("assets").addSnapshotListener { (QuerySnapshot, error) in
guard let documents = QuerySnapshot?.documents else {
print("No assets in here")
return
}
self.assets = documents.map { (QueryDocumentSnapshot) -> Asset in
let data = QueryDocumentSnapshot.data()
let brand = data["brand"] as? String ?? ""
let description = data["description"] as? String ?? ""
let date = data["date"] as? String ?? ""
let cost = data["cost"] as? String ?? ""
let serial = data["serial"] as? String ?? ""
let warranty = data["warranty"] as? String ?? ""
let warranty_cost = data["warranty_cost"] as? String ?? ""
return Asset(brand: brand, description: description, date: date, cost: cost, serial: serial, warranty: warranty, warranty_cost: warranty_cost)
}
}
}
}
And I have the following structure:
import Foundation
struct Asset: Identifiable {
var id: String = UUID().uuidString
var brand: String
var description: String
var date: String
var cost: String
var serial: String
var warranty: String
var warranty_cost: String
}
My main goal is to display the information like the first picture. I would appreciate any help given.
This is the code that I used to display the first picture:
import SwiftUI
struct ContentView: View {
#ObservedObject private var viewModel = AssetsViewModel()
var body: some View {
NavigationView {
List(viewModel.assets) { asset in
VStack(alignment: .leading) {
Text(asset.brand)
.font(.headline)
Text(asset.description)
.font(.subheadline)
Text(asset.date)
.font(.subheadline)
Text(asset.cost)
.font(.subheadline)
Text(asset.serial)
.font(.subheadline)
Text(asset.warranty)
.font(.subheadline)
Text(asset.warranty_cost)
.font(.subheadline)
}
}
.navigationBarTitle("Assets")
.onAppear() {
self.viewModel.fetchData()
}
}
}
}
struct AssetViewSwiftUIView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The post below helped me. The error I was getting was because Swift did not like me naming the ViewTable "assetList". Once I changed the name to "tableView" and changed in the code, it worked well. I added extra code to make the cells auto adjust from here: why UITableViewAutomaticDimension not working?.
Thank you very much for your help!
You need something like this:
import Foundation
import UIKit
import FirebaseFirestore
class myTableCell: UITableViewCell {
#IBOutlet weak var brand: UILabel!
#IBOutlet weak var description: UILabel!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var cost: UILabel!
#IBOutlet weak var serial: UILabel!
#IBOutlet weak var warranty: UILabel!
#IBOutlet weak var warrantyCost: UILabel!
}
class IndexAssets {
var brand = ""
var description = ""
var date = ""
var cost = ""
var serial = ""
var warranty = ""
var warrantyCost = ""
init(brand: String, description: String, date: String, cost: String, serial: String, warranty: String, warrantyCost: String)
{
self.brand = brand
self.description = description
self.date = date
self.cost = cost
self.serial = serial
self.warranty = warranty
self.warrantyCost = warrantyCost
}
}
class AssetViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var assetList: UITableView!
var dataArray: [IndexAssets] = [IndexAssets]()
override func viewDidLoad() {
super.viewDidLoad()
downloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! myTableCells
cell.brand?.text = dataArray[indexPath.row].brand
cell.description?.text = dataArray[indexPath.row].description
cell.date?.text = dataArray[indexPath.row].date
cell.cost?.text = dataArray[indexPath.row].cost
cell.serial?.text = dataArray[indexPath.row].serial
cell.warranty?.text = dataArray[indexPath.row].warranty
cell.warrantyCost?.text = dataArray[indexPath.row].warrantyCost
return cell
}
func downloadData()
{
let db = Firestore.firestore()
db.collection("assets").getDocuments { (snapshot, error) in
if error != nil
{
print("error")
}
else
{
for document in (snapshot?.documents)!
{
let brand = document.data() ["brand"] as? String
let description = document.data() ["description"] as? String
let date = document.data() ["date"] as? String
let cost = document.data() ["cost"] as? String
let serial = document.data() ["serial"] as? String
let warranty = document.data() ["warranty"] as? String
let warrantyCost = document.data() ["warranty_cost"] as? String
if let brand = brand, let description = description, let date = date, let cost = cost, let serial = serial, let warranty = warranty, let warrantyCost = warrantyCost
{
let insert = IndexAssets(brand: brand, description: description, date: date, cost: cost, serial: serial, warranty: warranty, warrantyCost: warrantyCost)
self.dataArray.append(insert)
}
}
self.assetList.reloadData()
}}
}
}
Also, remember in Storyboard to:
Assign "cell" to your Dynamic Cell identifier
Select myTableCell as Class for that cell
Drag labels from the class to connect them to storyboard
Drag your table to your View Controller (Yellow Icon on top) and select DataSource and Delegate.

create Realm DB for each user in Chat App

this one for sending message and save it to realm db
var messageIndex = try! Realm().objects(MessageRealm.self).sorted(byKeyPath: "timeStamp")
func didPressSend(text: String) {
if self.inputContinerView.inputTextField.text! != "" {
let messageDB = MessageRealm()
let realm = try! Realm()
let userRealm = UsersRealm()
messageDB.textDownloadded = text
messageDB.fromId = user!.fromId
messageDB.timeStamp = Date()
print(messageDB)
try! realm.write ({
print(realm.configuration.fileURL)
userRealm.msgs.append(messageDB)
//realm.create(MessageRealm.self, value: ["textDownloadded": text, "fromId": user!.fromId, "timeStamp": Date()])
})
if let userTitleName = user?.toId {
print(userTitleName)
OneMessage.sendMessage(text, thread: "AAAWatree", to: userTitleName, isPhoto: false, isVideo: false, isVoice: false, isLocation: false, timeStamp: date, completionHandler: { (stream, message) in
DispatchQueue.main.async {
OneMessage.sharedInstance.deleteCoreDataMessage()
}
self.inputContinerView.inputTextField.text! = ""
})
}
}
}
This for when recieving message im trying to save user (send id )
let realm = try! Realm()
userData.sender = sender
userData.toId = toUser
print(userData.sender)
print(userData.toId)
try! realm.write ({
realm.add(userData, update: true)
})
this my Realm Object Class
class MessageRealm: Object {
dynamic var textDownloadded = String()
dynamic var imageDownloadded = NSData()
dynamic var videoDownloadded = String()
dynamic var voiceDownloadded = String()
dynamic var fromId = String()
dynamic var timeStamp = Date()
dynamic var messageId = NSUUID().uuidString
let userSelect = List<UsersRealm>()
override class func primaryKey() -> String? {
return "messageId"
}
}
class UsersRealm: Object {
dynamic var sender = String()
dynamic var fromId = String()
dynamic var toId = String()
dynamic var lastMessage = String()
dynamic var timeStamp = Date()
dynamic var profileImage = NSData()
let msgs = List<MessageRealm>()
override class func primaryKey() -> String {
return "sender"
}
}
sending and reciving is ok and its save to realm db but all any user send message i recived in one user i want to seprate for every user have his sending and recive database i miss something here but i dont know i try to search nothing its long question but i cant figure out the soluation
and sorry for my week english
Thank you
If I understood your case correctly you're using a single realm url for all users that's why all your clients have the same data. You should probably create a separate realm for the conversation and share it between the users who participate in that chat. Please learn more about sharing realms in our docs at https://realm.io/docs/swift/latest/#access-control.

how to use If let in optional photo?

So, my app crash because it forces to unwrap the "photo" even if it is optional and it has nothing inside. How do I use the if let statement, like if the photo has a picture then it will show, and if none it will be nil but the app wont crash.
I have this struct User this is where my data is saved I am using firebase.
struct User {
var fullName: String!
var username: String?
var email: String!
var country: String?
var photoURL: String?
var biography: String?
var uid: String!
var ref: FIRDatabaseReference?
var key: String?
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
ref = snapshot.ref
fullName = (snapshot.value! as! NSDictionary) ["fullName"] as! String
email = (snapshot.value! as! NSDictionary) ["email"] as! String
uid = (snapshot.value! as! NSDictionary) ["uid"] as! String
country = (snapshot.value! as! NSDictionary) ["country"] as! String?
biography = (snapshot.value! as! NSDictionary) ["biography"] as! String?
photoURL = (snapshot.value! as! NSDictionary) ["photoURL"] as! String?
username = (snapshot.value! as! NSDictionary) ["username"] as! String?
}
}
this is where the app crashes, because of the "self.storageRef.reference(forURL:imageURL!)" it forces to unwrap even it has nothing inside.
func loadUserInfo() {
#IBOutlet weak var userImageView: UIImageView!
#IBOutlet weak var addButton: UIButton!
#IBOutlet weak var fullName: UILabel!
#IBOutlet weak var username: UILabel!
#IBOutlet weak var country: UILabel!
#IBOutlet weak var biography: UILabel!
let userRef = dataBaseRef.child("users/\(FIRAuth.auth()!.currentUser!.uid)")
userRef.observe(.value, with: { (snapshot) in
let user = User(snapshot: snapshot)
self.username.text = user.username
self.country.text = user.country
self.biography.text = user.biography
self.fullName.text = user.fullName
var imageURL = user.photoURL
self.storageRef.reference(forURL:imageURL!).data(withMaxSize: 1 * 1024 * 1024, completion: { (imgData, error) in
if error == nil {
DispatchQueue.main.async {
if let data = imgData {
self.userImageView?.image = UIImage(data: data)
}
}
} else {
print(error!.localizedDescription)
}
})
})
{ (error) in
print(error.localizedDescription)
}
}
first I think you should change the init of the User, you should do:
let data = snapshot.value as! NSDictionary;
fullName = data["fullName"] as! String;
if you not sure the country whether exist, you could do:
country = data["country"] as? String;
then you could use let to keep save when you use the use.photoURL, just like:
if let photoURL = user.photoURL {
//...retrive photo of the url from somewhere
}
finally, I wanna say, you maybe make a mistake understand about the ? and !, or confusion with them.
? is when you think this variable/func maybe nil/can not be called, or when you make a type conversion, but you don't sure that must be success.
! is that you are sure it's exist or you will create it immediatelly. also you could comprehend it as unwrap the optional value, just cause of you make it absolute exist.
when we create our own model just like your User, you could make the columns impossible nil, you can do:
var fullName: String? = nil;
fullName = SomeJsonTypeData["fullName"] ?? "default value";
then when you use it, you dispense with any worry about it will be nil.
If just focusing on the photoURL issue, I think this may help you:
if let imageURL = user.photoURL {
self.storageRef.reference(forURL: imageURL).data(withMaxSize: 1 * 1024 * 1024, completion: { (imgData, error) in
if error == nil {
DispatchQueue.main.async {
if let data = imgData {
self.userImageView?.image = UIImage(data: data)
}
}
} else {
print(error!.localizedDescription)
}
})
}