Array of custom object is not being set - ios

I am trying to store an Object, CustomPinAnnotation into an array. I have declared it like this: var pinArray = [CustomPinAnnotation]()
I am calling Firebase in a function which gets a lot of locations in which I construct my CustomPin.
func loadStuff()
_REF_BASE.child("Objects").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot]{
for snap in snapshots {
if let dict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let obj = objModel(lobbyKey: key, dictionary: pokeDict)
for location in obj.locations {
let coord = location.locationCoordinate
let customPin = CustomPinAnnotation()
customPin.coordinate = coord.coordinate
customPin.title = obj.name
let time = NSDate(timeIntervalSince1970: location.time)
let dayTimePeriodFormatter = NSDateFormatter()
dayTimePeriodFormatter.dateFormat = "MMM dd YYYY hh:mm a"
let dateString = dayTimePeriodFormatter.stringFromDate(time)
customPin._detailedDescription = "Seen at " + dateString
customPin.subtitle = "Seen at \(dateString)"
customPin._pokemonName = obj.name
customPin._imageName = obj.name
customPin._successRate = location.successRate
customPin._upVotes = location.upVotes
customPin._downVotes = location.downVotes
customPin._caughtAtTime = dateString
self.pinToPass = customPin // send this custom pin with info
self.pinArray.append(customPin)
// self.mapView.addAnnotation(customPin);
}
}
}
}
}
})
My ViewDidLoad method
self.PokeMapView.zoomEnabled = true
loadStuff()
self.mapView.addAnnotations(pinArray)
self.mapView.showAnnotations(pinArray, animated: true)
The commented line //self.MapView.addAnnotation(customPin) currently works, but this is very inefficient, as it would pin objects on the map that are not in the current scope of the map itself. However, when I try adding the list of Annotations like so: MapView.addAnnotations(pinArray), nothing works and via the debugger i'm told that the array contains 0 elements.
It'd be greatly appreciated if somebody could help me, as i've been stuck on this for a while and it really causes poor performance.

Related

How to make an observe at the moment in Swift?

I have a problem in this code:
func calculaGastos() -> Float{
let idUser = Auth.auth().currentUser?.displayName
var total : Float = 0
let ref = Database.database().reference().child("Users").child(idUser!).child("Gastos")
ref.observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? NSDictionary
if(value != nil){
for i in value!{
let j = i.value as? NSDictionary
let precio = j?["precio"] as? Float
let fecha = j?["Fecha"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let date = dateFormatter.date(from: fecha!)
if(((date?.timeIntervalSinceNow)! * -1) < (30*24*3600)){
print("Total:" ,total)
total += precio!
}
}
}
print("Calculing in the function", total)
}
return total
}
Which is called in a override function in another view controller and the logs shows that in the print of the viewdidload is 0, but in the function print is show that is printed as 30 but return 0 all time, I believe that the problem is that it returns before enter in the observer but I'm not sure any solutions for this?
override func viewDidLoad() {
super.viewDidLoad()
nomUser.text = id?.displayName!
correoLabel.text = id?.email!
print("Calculing in View Controller", calculo.calculaBenef(), calculo.calculaGastos())
gastosField.text = String(calculo.calculaGastos())
benefField.text = String(calculo.calculaBenef())
// Do any additional setup after loading the view.
}
Here is my log:
Log
Within an app I'm currently working on, I ran into a similar issue. The solution was to implement a dispatch group in the function. I also changed the way your function returns total so that it's now being returned by a completion handler.
Try this instead:
func calculaGastos(completionHandler: #escaping (Float) -> Void){
let idUser = Auth.auth().currentUser?.displayName
var total : Float = 0
let ref = Database.database().reference().child("Users").child(idUser!).child("Gastos")
ref.observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? NSDictionary
if(value != nil){
let myGroup = DispatchGroup()
for i in value!{
myGroup.enter()
let j = i.value as? NSDictionary
let precio = j?["precio"] as? Float
let fecha = j?["Fecha"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let date = dateFormatter.date(from: fecha!)
if(((date?.timeIntervalSinceNow)! * -1) < (30*24*3600)){
print("Total:" ,total)
total += precio!
}
myGroup.leave()
}
myGroup.notify(queue: .main) {
print("Calculing in the function", total)
completionHandler(total)
}
}}
}
Call the function and use total like this:
override func viewDidLoad() {
super.viewDidLoad()
nomUser.text = id?.displayName!
correoLabel.text = id?.email!
print("Calculing in View Controller", calculo.calculaBenef())
calculo.calculaGastos { (total) in
print("calculaGastos total: \(total)")
gastosField.text = String(total)
benefField.text = String(calculo.calculaBenef())
}
// Do any additional setup after loading the view.
}
To my understanding:
observeSingleEvent is asynchronous, so it may or may not complete by the time return is called. Additionally, the for i in value starts only after observeSingleEvent is complete, so return is even more likely to be called before the tasks are completed. That's where DispatchGroup() and the completion handler come in.
When myGroup.enter() is called, it notifies DispatchGroup that a task has started. When myGroup.leave() is called, DispatchGroup is notified that the task has been completed. Once there have been as many .leave()s as .enter()s, the group is finished. Then myGroup notifies the main queue that the the group is finished, and then the completionHandler is called which returns total.
The completionHandler is also beneficial because of the way in which you're using calculaGastos. You're calling the function, and then you're using the return value to be displayed in a textField. Now that the completion handler is added, the textField.text is only being set after calculaGastos is complete and has returned total:
calculo.calculaGastos { (total) in
print("calculaGastos total: \(total)")
gastosField.text = String(total)
benefField.text = String(calculo.calculaBenef())
}
Hope that makes some sense! Glad the code worked for you.

Swift firebase timestamp cant show

I am able to retrieve and convert my timestamp that I got from firebase(i did a breakpoint at that line and my time shows 5/3/18,3:05 PM, so it's fine), below is my code:
func loadMsg() {
let toId = user!.id!
let fromId = Auth.auth().currentUser!.uid
let chatRoomId = (fromId < toId) ? fromId + "_" + toId : toId + "_" + fromId
let ref = Database.database().reference().child("privateMessages").child(chatRoomId)
ref.observe(.value) { (snapshot) in
ref.observeSingleEvent(of: .childAdded, with: { (datasnap) in
let lastMsgTime = (datasnap.value as! [String: AnyObject])["timestamp"] as? Double
// to get timestamp and convert to date and time
let x = lastMsgTime!
let date = NSDate(timeIntervalSince1970: x)
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
let time = formatter.string(from: date as Date)
self.message.timestamp = time //HERE IT CRASHES!!!!!
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
self.messages.removeAll()
for data in snapshot {
let newMsg = Message(dictionary: data.value as! [String: AnyObject])
self.messages.append(newMsg)
}
}
})
DispatchQueue.main.async {self.tableView.reloadData()}
}
}
However it crashes at the line which i commented above, i was supposed to show it on my tableviewCell in my message cell. My tableView cell was done by xib which looks like :
and the code of my tableviewCell would be:
{
self.message = message
if message.fromId == currentUser {
sentView.isHidden = false
sentMsgLabel.text = message.textMessages
receivedMsgLabel.text = ""
receivedView.isHidden = true
timeReceived.text = message.timestamp
timeSent.text = message.timestamp
} else {
sentView.isHidden = true
sentMsgLabel.text = ""
receivedMsgLabel.text = message.textMessages
receivedMsgLabel.isHidden = false
timeReceived.text = message.timestamp
timeSent.text = message.timestamp
}
}
So, why would it crash and says
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value"
my message class code:
class Message: NSObject {
var fromId: String?
var textMessages: String?
var timestamp: String?
var toId: String?
var message: Message!
var _messageKey: String!
init(dictionary: [String: Any]) {
self.fromId = dictionary["fromId"] as? String
self.textMessages = dictionary["textMessages"] as? String
self.toId = dictionary["toId"] as? String
self.timestamp = dictionary["timestamp"] as? String
}
init(messageKey: String, postData: Dictionary<String, AnyObject>) {
_messageKey = messageKey
if let message = postData["textMessages"] as? String {
textMessages = message
}
if let sender = postData["fromId"] as? String {
fromId = sender
}
}
}
I believe there isnt any issue with my firebase, main problem is at the tableview cell, why wouldnt it show?
Try to get timestamp from server like this:
ServerValue.timestamp()

Dynamically using specific variables with custom UITableViewCell

This question will be a follow-up of this previous one.
I'm at the point where the user can create UITableViewCells and enter an amount to a TextField. It is then printed to a label and should update a variable that will be used with another variable for a calculation (basically amount * currentPrice, this is a wallet).
I re-read Apple's doc about TableViews and re-opened the exercises I did when I followed the Swift course but I'm struggling to understand the principle here, so once again, I think I really need someone to explain differently than what I could read so my brain can understand.
How will the cell know what variable to use here:
Entering the amount:
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
let dNumber = formatter.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
walletTableViewCell.amountLabel.text = String(format:"%.8f", eNumber)
UserDefaults.standard.set(walletTableViewCell.amountLabel.text, forKey: "cellAmount")
walletTableViewCell.amountTextField.text = ""
}
Calculations and display:
func updateCellValueLabel(cryptoPrice: String) {
if walletTableViewCell.amountLabel.text == "" {
walletTableViewCell.amountLabel.text = "0.00000000"
}
let formatter1 = NumberFormatter()
formatter1.locale = Locale(identifier: "en_US")
let str = walletTableViewCell.amountLabel.text
let dNumber = formatter1.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
UserDefaults.standard.set(eNumber, forKey: "currentAmount")
guard let cryptoDoublePrice = CryptoInfo.cryptoPriceDic[cryptoPrice] else { return }
WalletViewController.bitcoinAmountValue = cryptoDoublePrice * eNumber
if WalletViewController.currencyCode != "" {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "\(WalletViewController.currencyCode)")
walletTableViewCell.cryptoValueLabel.text = formatter.string(from: NSNumber(value: WalletViewController.amountValue))
}
UserDefaults.standard.set(WalletViewController.walletDoubleValue, forKey: "walletValue")
}
What I can't process here is how can I save the amount entered to the corresponding crypto UserDefaults file, then load this amount to be used with the corresponding variable that has the current price for calculations.
I don't know how to use the correct data for the correct cell.
Should I make this a single function? Should I pass parameters to the function (how do I pass the correct parameters to the corresponding cell with delegate?)? Do I need an array of parameters that will be used at indexPath to the correct cell (but how do I know the order if the user creates cells at will?)?
I'm kind of lost here.

How do I assign a variable to a value in a child node from a Firebase Snapshot?

I am trying to minimize the amount of data requests I make to Firebase. I have a snapshot that pulls the following data:
snap (Hole1) {
GreenBackX = "40.196339";
GreenBackY = "-105.07354";
GreenCenterX = "40.196336";
GreenCenterY = "-105.073409";
GreenFrontX = "40.196342";
GreenFrontY = "-105.073283";
Hazard1 = {
CarryX = "40.196839";
CarryY = "-105.07104";
FrontX = "40.196893";
FrontY = "-105.070626";
Name = "R Fwy Bunker";
};
Hazard2 = {
CarryX = "40.196321";
CarryY = "-105.071922";
FrontX = "40.196383";
FrontY = "-105.071573";
Name = "L Fwy Bunker";
};
Hazard3 = {
CarryX = "40.196622";
CarryY = "-105.072935";
FrontX = "40.196662";
FrontY = "-105.072554";
Name = "R Fwy Bunker 2";
};
Hazard4 = {
CarryX = "40.196176";
CarryY = "-105.073545";
FrontX = "40.196225";
FrontY = "-105.073167";
Name = "L Greenside Bunker";
};
HoleH = "258.74";
HoleX = "40.19664";
HoleY = "-105.070484";
}
What I am aiming to do is assign a variable that would be Hazard1\Name. So far, I've only seen working examples in the docs showing how to grab the initial value, which I do like this: let holeX = value?["HoleX"] as? Double ?? 0, but I can't seem to find anything that gets me to the '2nd level' if you will.
There was one example that seemed to say you could do it by referencing it this way: let hazard1Name = value?["Hazard1/Name"] but I couldn't get it to work.
A FIRDataSnapshot contains data from a given Firebase Database location. The data has already been downloaded so there isn't much room for optimizations there, sorry :-(
I think what you might be looking for is:
let ref: FIRDatabaseReference = ...
ref.child("Hazard1").child("Name").observeSingleEvent(of: .value) {
(snapshot) in
let name = snapshot.value as! String
print("Name: \(name)")
}
Otherwise, if you just want to get your data out of the aforementioned snapshot, try this instead:
let snapshot: FIRDataSnapshot = ...
let data = snapshot.value as! [String: Any]
let hazard1 = data["Hazard1"] as! [String: Any]
let name = hazard1["Name"] as! String

Edgy Scrolling on UITableView - iOS

I'm making an application that populates the table view from a parse database. The problem is there's a bit of lag whenever I scroll. Through some searching, I've come to realize that the problem lies in the cellForRowAtIndexPath() function where I'm querying the database in order to get the data I need for that cell's labels. I have to query the database there because I use the cell's position to index into my messages array in order to grab the right message. Here's the meat of my cellForRowAtIndexPath() function:
if (path.row < messagesArray.count) {
var message = messagesArray[path.row]
var dateFormat = NSDateFormatter()
var query = PFQuery(className: "Messages")
query.whereKey("messageID", equalTo: message.messageID)
var first = query.getFirstObject()
if (message.senderID == loggedInUserID) {
cell.nameLabel?.text = "I said"
} else {
queryO?.whereKey("objectId", equalTo: message.senderID)
var second = queryO?.getFirstObject()
var name = second!.objectForKey("FIRST") as! String
var middle = second!.objectForKey("MIDDLE") as! String?
var last = second!.objectForKey("LAST") as! String
if (middle != nil) {
cell.nameLabel?.text = name + " " + middle! + " " + last
} else {
cell.nameLabel?.text = name + " " + last
}
}
cell.messageLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
cell.messageLabel?.text = message.messageText
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy 'at' h:mm a" // superset of OP's format
let str = dateFormatter.stringFromDate(first!.objectForKey("TIME") as! NSDate)
cell.senderLabel?.text = str
}
Any ideas?
What I ended up doing was I took the query outside my function and ran it in my viewDidLoad() which improved the speed drastically. Thanks for your help guys :-)

Resources