Swift Firebase completion handler not working - swift

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.

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

Extracting data from csv file to put on UI table view

I was wondering if I could ask a question regarding csv/http posting. I want my data to update within my app after I pull the data from my github csv file, but when I do, it doesn't update within the app but just prints the numbers. What's wrong with my code? Thanks!
class NewStats: UITableViewController {
var didTapMenuType: ((NewMenu) -> Void)?
var Num2:String = "2"
#IBOutlet weak var MultnomahText: UILabel!
#IBOutlet weak var MultnomahView: UIProgressView!
override func viewDidLoad() {
let url = URL(string: "SampleUrl.csv")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
let str = (String(data: data, encoding: .utf8)!)
let fullNameArr = str.components(separatedBy: ",")
let Num1 = fullNameArr[0]
self.Num2 = fullNameArr[1]
let Num3 = fullNameArr[2]
let Num4 = fullNameArr[3]
let Num5 = fullNameArr[4]
let Num6 = fullNameArr[5]
let Num7 = fullNameArr[6]
let Num8 = fullNameArr[7]
let Num9 = fullNameArr[8]
let Num10 = fullNameArr[9]
print(Num1);
print(self.Num2);
print(Num3);
print(Num4);
print(Num5);
print(Num6);
print(Num7);
print(Num8);
print(Num9);
print(Num10);
}
task.resume()
super.viewDidLoad()
DispatchQueue.main.async {
self.tableView.reloadData()
}
print(self.Num2);
self.MultnomahText.text = "Multnomah:" + self.Num2
self.MultnomahView.setProgress(0.5, animated: true)
}
After the print(Num10)
DispatchQueue.main.async {
self.MultnomahText.text = "Multnomah:" + self.Num2
self.MultnomahView.setProgress(0.5, animated: true)
self.tableView.reloadData()
}
Hopefully this helps!

How to add a value in Firebase to a Label in xcode?

I have been Searching for 2 days on the internet now, and no sign of this. All i want to do is to take a value from RealTimeDataBase and put it in my label, apparently i can't find an answer and the people in youtube do it in a TableViewController. I tried some code but it doesn't work:
my code:
struct mylist {
let title : String!
}
var ref: DatabaseReference?
#IBOutlet var LabelTest: UILabel!
var posts = [mylist]()
#IBAction func nextTapped(_ sender: Any) {
//
//
let ref = Database.database().reference()
ref.child("ServiceA").queryOrderedByKey().observeSingleEvent(of: .value, with: { DataSnapshot in
let value = DataSnapshot.value as? NSDictionary
let title = value?["ServiceA"] as? String
self.posts.insert(mylist(title: title), at: 0)
})
post()
}
func post() {
let title = "Title"
let post = ["title": title]
let ref = Database.database().reference()
ref.child("ServiceA").childByAutoId().setValue(post)
LabelTest.text = mylist.title
}
please help me
i tried to follow this tutorial: https://www.youtube.com/watch?v=XIQsQ2injLo
FINALLY!!!
ok so here's how i did it:
let ref = Database.database().reference()
func getIncome() {
ref.child("ServiceA").child("Title").observeSingleEvent(of: .value, with: { DataSnapshot in
print(DataSnapshot)
let m = DataSnapshot.value as? String
self.LabelTest.text = m
})
}
ok special thanks to koen, his method works to :)

Firebase data to tableview, crash after appending data

I am trying to download data from Firebase using
firebaseDB.observe(DataEventType.value, with: { (snapshot) in })
which works well. I am able to receive the following data:
let fetchedObject = items.value as? [String: AnyObject]
let dbUrl = fetchedObject?["url"]
let dbTime = fetchedObject?["time"]
let dbStatus = fetchedObject?["status"]
Using print("\(dbUrl!), \(dbTime!), \(dbStatus!)"), I see the following output in the console:
https://google.de, 2019-04-26 07:44:54 +0000, new
However, because I want to show the data in a Tableview, I have created a swift file for a data model, called ItemModel with the following content:
class ItemModel {
var url: String?
var time: String?
var status: String?
init(url: String?, time: String?, status: String?){
self.url = url
self.time = time
self.status = status
}
}
When trying to populate the url, time and status using
let item = ItemModel(url: dbUrl as! String?, time: dbTime as! String?, status: dbStatus as! String?)
appending it with self.itemList.append(item), and reloading the table with self.tableViewOutlet.reloadData(), the app crashes.
If I comment out self.itemList.append(item), it does not crash.
What am I doing wrong? I am clearly missing something viable, but I just can't figure out what. I am really wondering why appending the data does not work... Thanks a lot for any help!
EDIT:
Complete fetching method:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var itemList = [ItemModel]()
override func viewDidLoad() {
super.viewDidLoad()
[...]
// Authenticate user
Auth.auth().signInAnonymously() {(authResult, error) in
if error == nil {
self.user = authResult!.user // Authenticate User
self.uid = self.user.uid // Users unique and anonymous identifier by Firebase
print(">> LOG: Auth success!")
// Download data from Firebase
var itemsDB: DatabaseReference!
itemsDB = Database.database().reference().child("somechild").child("someotherchild")
itemsDB.observe(DataEventType.value, with: { (snapshot) in
// Check if there is any relevant data
if snapshot.childrenCount > 0 {
// Initially clear the list
self.itemList.removeAll()
// Interate through all the values
for items in snapshot.children.allObjects as! [DataSnapshot] {
// Get the values from Firebase
let fetchedObject = items.value as? [String: AnyObject]
let dbUrl = fetchedObject?["url"]
let dbTime = fetchedObject?["time"]
let dbStatus = fetchedObject?["status"]
let item = ItemModel(url: dbUrl as? String, time: dbTime as? String, status: dbStatus as? String)
print("\(item.url!), \(item.time!), \(item.status!)")
self.itemList.append(item)
}
// Reload the Tableview
self.tableViewOutlet.reloadData()
}
})
} else {
print(">> LOG: Error when trying to authenticate: \(error!)")
}
}
}
}

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