how to use If let in optional photo? - swift

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)
}
})
}

Related

problem with adding element to swift array

I am creating an application. I make a request to the firebase store, after that I add the result to the array, but in the end, when the array is displayed from viewDidLoad or other functions, I get an empty array. But if you make a conclusion immediately after the request, then everything is displayed correctly
`
import UIKit
import Firebase
import FirebaseStorage
import FirebaseFirestore
class CatalogVC: UIViewController {
struct Item: Codable {
var title: String
var price: Int
var description: String
var imageUrl: String
}
#Published var items: [Item] = []
let database = Firestore.firestore()
#IBOutlet weak var textViewCatalog: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let settings = FirestoreSettings()
Firestore.firestore().settings = settings
itemsList()
print(items)
showCatalogVC()
}
#IBAction func showCatalogTapped() {
}
private func showCatalogVC() {
print("SHOW CATALOG")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let dvc = storyboard.instantiateViewController(withIdentifier: "CatalogVC") as! CatalogVC
self.present(dvc, animated: true, completion: nil)
}
func itemsList(){
database.collection("catalog")
.getDocuments { (snapshot, error) in
self.items.removeAll()
if let snapshot {
for document in snapshot.documents{
let docData = document.data()
let title: String = docData["title"] as? String ?? ""
let imageUrl: String = docData["imageUrl"] as? String ?? ""
let description: String = docData["description"] as? String ?? ""
let price: Int = docData["price"] as? Int ?? 0
let item: Item = Item(title: title, price: price, description: description, imageUrl: imageUrl)
self.items.append(item)
}
}
}
}
}
`
I am creating an application. I make a request to the firebase store, after that I add the result to the array, but in the end, when the array is displayed from viewDidLoad or other functions, I get an empty array. But if you make a conclusion immediately after the request, then everything is displayed correctly
Getting data from Firebase is asynchronous process. So here you should make everything after loading data in closure database.collection("catalog").getDocuments {...}.
func itemsList(){
database.collection("catalog")
.getDocuments { (snapshot, error) in
self.items.removeAll()
if let snapshot {
for document in snapshot.documents{
let docData = document.data()
let title: String = docData["title"] as? String ?? ""
let imageUrl: String = docData["imageUrl"] as? String ?? ""
let description: String = docData["description"] as? String ?? ""
let price: Int = docData["price"] as? Int ?? 0
let item: Item = Item(title: title, price: price, description: description, imageUrl: imageUrl)
self.items.append(item)
}
}
print(self.items) //print items to see them
//here use items data
}
}

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.

Struggling to pass data to UI - 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.

unable to send Firebase document ID in from VC to VC

i'm running a query to firebase and i'm able to obtain the documentID but i'm not able to send the documentID over to another VC. I get everything int he query with no issues. I'm not sure what i'm missing. Any suggestions are greatly appreciated.
First VC
#IBAction func getDataTapped(_ sender: Any) {
SVProgressHUD.show()
if HOSP != (hospNameTxt.text!) {
ptListQuery = ptListCollectionRef?.whereField("hosp", isEqualTo: (hospNameTxt.text!))
}
ptListQuery?.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("error getting data: \(err)")
} else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let ptName = data[PTNAME] as? String ?? ""
let assignedMd = data[ASSIGNEDMD] as? String ?? ""
let officeMd = data[OFFICEMD] as? String ?? ""
let assignedDate = data[ASSIGNEDDATE] as? String ?? ""
let seeNoSee = data[SEENOSEE] as? String ?? ""
let room = data[ROOM] as? String ?? ""
let app = data[APP] as? String ?? ""
let documentId = document.documentID
let username = data[USERNAME] as? String ?? ""
let userId = data[USER_ID] as? String ?? ""
let newPtList = PTList(ptName: ptName,
assignedMd: assignedMd,
officeMd: officeMd,
assignedDate: assignedDate,
seeNoSee: seeNoSee,
room: room,
app: app, documentId: documentId,
username: username, userId: userId
)
self.ptListInCell.append(newPtList)
print("docID", documentId) //this shows the documentID in the console
print(document.data()) //this shows everything in the array but the document ID
}
}
self.performSegue(withIdentifier: "goToResults", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToResults" {
let vc = segue.destination as! ResultsdataVC
vc.ptListFromCell = ptListInCell
SVProgressHUD.dismiss()
}
}
Second VC
class ResultsdataVC: UIViewController, UITableViewDataSource, UITableViewDelegate, PatListCellDelegate {
#IBOutlet weak var resultsTableView: UITableView!
#IBOutlet weak var patientFilter: UISegmentedControl!
var ptListFromCell = [PTList]()
var ptDatasToPass = [PTData]()
var selectedFilter = FilterCategory.hosp.rawValue
var patientListener: ListenerRegistration!
var patientCollectionRef: CollectionReference!
//segmentedControl
var dataFilter = 0
var tableDataSee : [String] = ["Yes"]
var tableDataNoSee : [String] = ["No"]
override func viewDidLoad() {
super.viewDidLoad()
resultsTableView.delegate = self
resultsTableView.dataSource = self
resultsTableView.rowHeight = 110
}
here is PTList model:
class PTList {
private(set) var ptName: String!
private(set) var assignedMd: String!
private(set) var officeMd: String!
private(set) var assignedDate: String!
private(set) var seeNoSee: String!
private(set) var room: String!
private(set) var app: String!
private(set) var documentId: String!
private(set) var username: String!
private(set) var userId: String!
init(ptName: String, assignedMd: String, officeMd: String, assignedDate: String, seeNoSee: String, room: String, app: String, documentId: String, username: String, userId: String) {
self.ptName = ptName
self.assignedMd = assignedMd
self.officeMd = officeMd
self.assignedDate = assignedDate
self.seeNoSee = seeNoSee
self.room = room
self.app = app
self.documentId = documentId
self.username = username
self.userId = userId
}
}
here is my db structure

Swift Firebase completion handler not working

My code calls Firebase and gets user information and user images. Once that's loaded, the app populates an image field and a text label. At least, that's what's supposed to happen. But instead, I'm getting fatal error: Index out of range error. What am I missing?
Here's my code:
#IBOutlet weak var userImage: UIImageView!
#IBOutlet weak var instructionsLabel: UILabel!
var currentUserName: String!
func loadExistingUsers(completion: #escaping () -> ()) {
ref.child("members").observe(.childAdded) { (snapshot: FIRDataSnapshot) in
if let dict = snapshot.value as? [String : Any] {
let userPhotoUrl = dict["profileImageUrl"] as! String
let userFirstName = dict["firstName"] as! String
let userBirthday = dict["birthday"] as! Int
let userPasscode = dict["passcode"] as! Int
let userGender = dict["gender"] as! String
let isUserChildOrParent = dict["childParent"] as! String
let storageRef = FIRStorage.storage().reference(forURL: userPhotoUrl)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
let pic = UIImage(data: data!)
let user = User(profilePhoto: pic!,
userFirstName: userFirstName,
userBirthday: userBirthday,
userPasscode: userPasscode,
userGender: userGender,
isUserChildOrParent: isUserChildOrParent)
self.users.append(user)
self.users.sort(by: {$0.birthday < $1.birthday})
})
completion()
}
}
}
and my ViewDidLoad call:
loadExistingUsers {
self.currentUserName = self.users[0].firstName // error here
self.instructionsLabel.text = "Choose daily and weekly job assignments for \(self.users[0].firstName)."
self.userImage.image = self.users[0].photo
}
What am I doing wrong? I know my base code is good because I've tested it. Is it just the completion handler that's the problem? Any help would be greatly appreciated.