I want to multiply the two firebase values and get the result to display in the Label, without success
Can anyone give me some advice
func UserInfo(){
let firestoreDB = Firestore.firestore()
firestoreDB.collection("Usermoney").whereField("email", isEqualTo: Auth.auth().currentUser!.email!).addSnapshotListener { snapshot, error in
if error != nil {
print(error?.localizedDescription ?? "Error while getting data from server!" )
}else{
if snapshot?.isEmpty != true && snapshot != nil {
for document in snapshot!.documents{
if let money = document.get("money") as? String{
if let q8 = document.get("q8") as? String{
self.money.text = money
self.q8.text = "+"+q8+"%"
let tip = money * q8
self.win.text = String(format: "$%.2f", tip)
}
}
}
}
}
}
}
Try This
func UserInfo(){
let firestoreDB = Firestore.firestore()
firestoreDB.collection("Usermoney").whereField("email", isEqualTo: Auth.auth().currentUser!.email!).addSnapshotListener { snapshot, error in
if error != nil {
print(error?.localizedDescription ?? "Error while getting data from server!" )
}else{
if snapshot?.isEmpty != true && snapshot != nil {
for document in snapshot!.documents{
if let money = document.get("money") as? String{
if let q8 = document.get("q8") as? String{
self.money.text = money
self.q8.text = "+"+q8+"%"
if let moneyDouble = Double(money), let q8Double = Double(q8){
let tip = moneyDouble * q8Double
self.win.text = String(format: "$%.2f", tip)
}
}
}
}
}
}
}
}
When I am using addSnapshotListener for realtime updates, the documents are repeated which should not be the case, but when using getDocuments() the documents are repeated once only, I need to use addSnaphotListener but not want to duplicate the document reading, please assist where I am wrong in using snapshot listener.
I am using Firestore database in Swift iOS. Below is the code I am using
Code with addSnapShotListener():
func getComments() {
//print(postId + "received")
let commentsRef = Firestore.firestore().collection("posts").document(postId).collection("comments")
commentsRef.addSnapshotListener { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
// self.length = snapshot.count
let data = document.data()
let username = data["comment_author_username"] as? String ?? ""
let comment = data["comment_author_comment"] as? String ?? ""
let spinnerC = data["comment_author_spinnerC"] as? String ?? ""
let fullname = data["comment_author_fullname"] as? String ?? ""
let email = data["comment_author_email"] as? String ?? ""
let commentUserImageUrl = data["comment_user_image"] as? String ?? ""
let commentuser_id = data["comment_author_id"] as? String ?? ""
self.checkl1value = data["l1"] as? Bool
let newComment = Comment(_documentId: document.documentID, _commentAuthorUsername: username, _commentAuthorFullName: fullname, _commentAuthorComment: comment, _commentUserImage: commentUserImageUrl, _commentAuthorSpinnerC: spinnerC, _commentAuthorId:commentuser_id, _checkl1value: self.checkl1value)
self.comments.append(newComment)
// print(self.length!)
}
self.tableView.reloadData()
}
}
}
}
Code With getDocuments():
func getComments() {
//print(postId + "received")
let commentsRef = Firestore.firestore().collection("posts").document(postId).collection("comments")
commentsRef.getDocuments { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
// self.length = snapshot.count
let data = document.data()
let username = data["comment_author_username"] as? String ?? ""
let comment = data["comment_author_comment"] as? String ?? ""
let spinnerC = data["comment_author_spinnerC"] as? String ?? ""
let fullname = data["comment_author_fullname"] as? String ?? ""
let email = data["comment_author_email"] as? String ?? ""
let commentUserImageUrl = data["comment_user_image"] as? String ?? ""
let commentuser_id = data["comment_author_id"] as? String ?? ""
self.checkl1value = data["l1"] as? Bool
let newComment = Comment(_documentId: document.documentID, _commentAuthorUsername: username, _commentAuthorFullName: fullname, _commentAuthorComment: comment, _commentUserImage: commentUserImageUrl, _commentAuthorSpinnerC: spinnerC, _commentAuthorId:commentuser_id, _checkl1value: self.checkl1value)
self.comments.append(newComment)
// print(self.length!)
}
self.tableView.reloadData()
}
}
}
}
You're probably looking to only handle the changes between the snapshots. To do that you'll want to loop over instead of, as shown in the documentation on viewing changes between snapshots:
db.collection("cities").whereField("state", isEqualTo: "CA")
.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
print("New city: \(diff.document.data())")
}
if (diff.type == .modified) {
print("Modified city: \(diff.document.data())")
}
if (diff.type == .removed) {
print("Removed city: \(diff.document.data())")
}
}
}
Initially your listener will get called with diff.type == .added for each existing document, and then when there are changes it'll get called with the right mix of types.
I want to translate googleMap Api result for location address to Persian. i'm using YandexApi for translate address. separate address components with "،" and pass this array to for api. my problem is after every service call my userInterface updated. how can solve this problem.
for example when I pass this array to method:["road","avenue"]
address label text update after every translate response
first text for label = "جاده"
last text for label = "جاده ، خیابان"
there is my code:
func translateText(text:[String],closure:#escaping ((_ success:String?,_ error:Error?) -> Void)) {
var translateString: String = ""
var responseError: Error?
for item in text {
myGroup.enter()
let urlString = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=trnsl.1.1.20171105T134956Z.795c7a0141d3061b.dc25bae76fa5740b2cdecb02396644dea58edd24&text=\(item)&lang=fa&format=plain&options=1"
if let allowString = Utilities.shareInstance.getQueryAllowedString(url: urlString) {
if let url = URL(string:allowString){
Alamofire.request(url).responseJSON { response in
guard let responseData = response.data else {
return
}
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: [])
if let res = json as? [String:Any] {
if let code = res["code"] as? Int {
if code == 200 {
if let textArr = res["text"] as? [AnyObject] {
let flattArr = Utilities.shareInstance.flatStringMapArray(textArr)
if flattArr.count > 0 {
translateString += " ، " + flattArr[0]
}
}
}
}
}
}catch {
responseError = error
}
self.myGroup.leave()
closure(translateString, responseError)
}
}
}
}
}
private func translatingAddressArrayForChecking(address:[String]) {
var addressString = ""
let addressComponent = address
if addressComponent.count > 0 {
YandexTranslateAPI.shateInstance.translateText(text: addressComponent, closure:{ success,_ in
if let res = success {
addressString = res
OperationQueue.main.addOperation {
self.layoutActivity(start: false)
if self.searchState == .Origin && self.destinationButton.isHidden == false{
if let selectOrigin = self.originSelectedCity {
if let city = selectOrigin.city_title {
if addressString.contains(city) {
self.originAddressLabel.text = addressString
if let location = self.startCoordinate {
self.setOrigin(location: location)
}
}else {
self.CreateCustomTopField(text: "wrong city", color: Constants.ERROR_COLOR)
}
}
}
}else if self.searchState == .Destination || self.destinationButton.isHidden == true {
if let selectOrigin = self.destinationSelectedCity {
if let city = selectOrigin.city_title {
if addressString.contains(city) {
self.destinationAddressLabel.text = addressString
if let location = self.endCoordinate {
self.setDestination(location: location)
}
}else {
self.CreateCustomTopField(text: "wrong city", color: Constants.ERROR_COLOR)
}
}
}
}
}
}
})
}
addresses label text update multiple with array component.
I am trying to generate a Formatted Full address using CLGeocoder in Swift 3. I referred to this SO thread to get the code given below.
However, sometimes the app crashes with a 'nil' error at the line:
//Address dictionary
print(placeMark.addressDictionary ?? "")
Questions:
How can I concatenate these values retrieved from the GeoCoder to form a full address? (Street + City + etc)
How do I handle the nil error I get when the func is unable to find an address?
Full code:
func getAddress() -> String {
var address: String = ""
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: selectedLat, longitude: selectedLon)
//selectedLat and selectedLon are double values set by the app in a previous process
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// Address dictionary
//print(placeMark.addressDictionary ?? "")
// Location name
if let locationName = placeMark.addressDictionary!["Name"] as? NSString {
//print(locationName)
}
// Street address
if let street = placeMark.addressDictionary!["Thoroughfare"] as? NSString {
//print(street)
}
// City
if let city = placeMark.addressDictionary!["City"] as? NSString {
//print(city)
}
// Zip code
if let zip = placeMark.addressDictionary!["ZIP"] as? NSString {
//print(zip)
}
// Country
if let country = placeMark.addressDictionary!["Country"] as? NSString {
//print(country)
}
})
return address;
}
func getAddressFromLatLon(pdblLatitude: String, withLongitude pdblLongitude: String) {
var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
let lat: Double = Double("\(pdblLatitude)")!
//21.228124
let lon: Double = Double("\(pdblLongitude)")!
//72.833770
let ceo: CLGeocoder = CLGeocoder()
center.latitude = lat
center.longitude = lon
let loc: CLLocation = CLLocation(latitude:center.latitude, longitude: center.longitude)
ceo.reverseGeocodeLocation(loc, completionHandler:
{(placemarks, error) in
if (error != nil)
{
print("reverse geodcode fail: \(error!.localizedDescription)")
}
let pm = placemarks! as [CLPlacemark]
if pm.count > 0 {
let pm = placemarks![0]
print(pm.country)
print(pm.locality)
print(pm.subLocality)
print(pm.thoroughfare)
print(pm.postalCode)
print(pm.subThoroughfare)
var addressString : String = ""
if pm.subLocality != nil {
addressString = addressString + pm.subLocality! + ", "
}
if pm.thoroughfare != nil {
addressString = addressString + pm.thoroughfare! + ", "
}
if pm.locality != nil {
addressString = addressString + pm.locality! + ", "
}
if pm.country != nil {
addressString = addressString + pm.country! + ", "
}
if pm.postalCode != nil {
addressString = addressString + pm.postalCode! + " "
}
print(addressString)
}
})
}
Formatting addresses is hard because each country has its own format.
With a few lines of code, you can get the correct address format for each country and let Apple handle the differences.
Since iOS 11, you can get a Contacts framework address:
extension CLPlacemark {
#available(iOS 11.0, *)
open var postalAddress: CNPostalAddress? { get }
}
This extension is part of the Contacts framework.
This means, this feature is invisible to you in the XCode code completion until you do
import Contacts
With this additional import, you can do something like
CLGeocoder().reverseGeocodeLocation(location, preferredLocale: nil) { (clPlacemark: [CLPlacemark]?, error: Error?) in
guard let place = clPlacemark?.first else {
print("No placemark from Apple: \(String(describing: error))")
return
}
let postalAddressFormatter = CNPostalAddressFormatter()
postalAddressFormatter.style = .mailingAddress
var addressString: String?
if let postalAddress = place.postalAddress {
addressString = postalAddressFormatter.string(from: postalAddress)
}
}
and get the address formatted in the format for the country in the address.
The formatter even supports formatting as an attributedString.
Prior to iOS 11, you can convert CLPlacemark to CNPostalAddress yourself and still can use the country specific formatting of CNPostalAddressFormatter.
This is my code for swift 3
func getAdressName(coords: CLLocation) {
CLGeocoder().reverseGeocodeLocation(coords) { (placemark, error) in
if error != nil {
print("Hay un error")
} else {
let place = placemark! as [CLPlacemark]
if place.count > 0 {
let place = placemark![0]
var adressString : String = ""
if place.thoroughfare != nil {
adressString = adressString + place.thoroughfare! + ", "
}
if place.subThoroughfare != nil {
adressString = adressString + place.subThoroughfare! + "\n"
}
if place.locality != nil {
adressString = adressString + place.locality! + " - "
}
if place.postalCode != nil {
adressString = adressString + place.postalCode! + "\n"
}
if place.subAdministrativeArea != nil {
adressString = adressString + place.subAdministrativeArea! + " - "
}
if place.country != nil {
adressString = adressString + place.country!
}
self.lblPlace.text = adressString
}
}
}
}
You can esaily call above funcation like:
let cityCoords = CLLocation(latitude: newLat, longitude: newLon)
cityData(coord: cityCoords)
For fixing the empty address issue, either you can use a class property to hold the appended value or you can use a closure to return the value back to the calling function
For fixing the crash you need to avoid the force unwrapping of optionals
Using a closure you can do it like:
// Using closure
func getAddress(handler: #escaping (String) -> Void)
{
var address: String = ""
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: selectedLat, longitude: selectedLon)
//selectedLat and selectedLon are double values set by the app in a previous process
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark?
placeMark = placemarks?[0]
// Address dictionary
//print(placeMark.addressDictionary ?? "")
// Location name
if let locationName = placeMark?.addressDictionary?["Name"] as? String {
address += locationName + ", "
}
// Street address
if let street = placeMark?.addressDictionary?["Thoroughfare"] as? String {
address += street + ", "
}
// City
if let city = placeMark?.addressDictionary?["City"] as? String {
address += city + ", "
}
// Zip code
if let zip = placeMark?.addressDictionary?["ZIP"] as? String {
address += zip + ", "
}
// Country
if let country = placeMark?.addressDictionary?["Country"] as? String {
address += country
}
// Passing address back
handler(address)
})
}
You can call the method like:
getAddress { (address) in
print(address)
}
To concatenate you can simply replace return address by this :
return "\(locationName), \(street), \(city), \(zip), \(country)"
Keeping it simple - A full Swift 3 & 4 compatible View Controller example for obtaining a formatted address string from user's location (add in the other keys available in CLPlacemark if you want more information in your string):
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let manager = CLLocationManager()
let geocoder = CLGeocoder()
var locality = ""
var administrativeArea = ""
var country = ""
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
manager.stopUpdatingLocation()
geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error) in
if (error != nil) {
print("Error in reverseGeocode")
}
let placemark = placemarks! as [CLPlacemark]
if placemark.count > 0 {
let placemark = placemarks![0]
self.locality = placemark.locality!
self.administrativeArea = placemark.administrativeArea!
self.country = placemark.country!
}
})
}
func userLocationString() -> String {
let userLocationString = "\(locality), \(administrativeArea), \(country)"
return userLocationString
}
}
Calling print(userLocationString()) in this example will print: suburb, state, country
Don't forget to add Privacy - Location When In Use Usage Description to your Info.plist file beforehand, to allow the user to grant permissions to your app to utilise location services.
Here's a 2-3 line version of the answers here:
func getAddress(placemarks: [CLPlacemark]) -> String {
guard let placemark = placemarks.first, !placemarks.isEmpty else {return ""}
let outputString = [placemark.locality,
placemark.subLocality,
placemark.thoroughfare,
placemark.postalCode,
placemark.subThoroughfare,
placemark.country].compactMap{$0}.joined(separator: ", ")
print(outputString)
return outputString
}
func getAddress(from coordinate: CLLocationCoordinate2D, completion: #escaping (String) -> Void) {
let geoCoder = CLGeocoder()
let location = CLLocation.init(latitude: coordinate.latitude, longitude: coordinate.longitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// check for errors
guard let placeMarkArr = placemarks else {
completion("")
debugPrint(error ?? "")
return
}
// check placemark data existence
guard let placemark = placeMarkArr.first, !placeMarkArr.isEmpty else {
completion("")
return
}
// create address string
let outputString = [placemark.locality,
placemark.subLocality,
placemark.thoroughfare,
placemark.postalCode,
placemark.subThoroughfare,
placemark.country].compactMap { $0 }.joined(separator: ", ")
completion(outputString)
})
}
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: vehicleLocation.latitude, longitude: vehicleLocation.latitude), completionHandler: {(placemarks, error) -> Void in
guard error == nil else {completionHandler(nil); return}
guard let place = placemarks else {completionHandler(nil); return}
if place.count > 0 {
let pm = place[0]
var addArray:[String] = []
if let name = pm.name {
addArray.append(name)
}
if let thoroughfare = pm.thoroughfare {
addArray.append(thoroughfare)
}
if let subLocality = pm.subLocality {
addArray.append(subLocality)
}
if let locality = pm.locality {
addArray.append(locality)
}
if let subAdministrativeArea = pm.subAdministrativeArea {
addArray.append(subAdministrativeArea)
}
if let administrativeArea = pm.administrativeArea {
addArray.append(administrativeArea)
}
if let country = pm.country {
addArray.append(country)
}
if let postalCode = pm.postalCode {
addArray.append(postalCode)
}
let addressString = addArray.joined(separator: ",\n")
print(addressString)
completionHandler(addressString)
}
else { completionHandler(nil)}
})
I create my own static class for Geocoding and get attributes of CLPlacemark and obtain a complete address, like "usually" returns Google:
import Foundation
import CoreLocation
class ReverseGeocoding {
static func geocode(latitude: Double, longitude: Double, completion: #escaping (CLPlacemark?, _ completeAddress: String?, Error?) -> ()) {
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude)) { placemarks, error in
guard let placemark = placemarks?.first, error == nil else {
completion(nil, nil, error)
return
}
let completeAddress = getCompleteAddress(placemarks)
completion(placemark, completeAddress, nil)
}
}
static private func getCompleteAddress(_ placemarks: [CLPlacemark]?) -> String {
guard let placemarks = placemarks else {
return ""
}
let place = placemarks as [CLPlacemark]
if place.count > 0 {
let place = placemarks[0]
var addressString : String = ""
if place.thoroughfare != nil {
addressString = addressString + place.thoroughfare! + ", "
}
if place.subThoroughfare != nil {
addressString = addressString + place.subThoroughfare! + ", "
}
if place.locality != nil {
addressString = addressString + place.locality! + ", "
}
if place.postalCode != nil {
addressString = addressString + place.postalCode! + ", "
}
if place.subAdministrativeArea != nil {
addressString = addressString + place.subAdministrativeArea! + ", "
}
if place.country != nil {
addressString = addressString + place.country!
}
return addressString
}
return ""
}
}
Then the implementation:
ReverseGeocoding.geocode(coordinate: coordinate, completion: { (placeMark, completeAddress, error) in
if let placeMark = placeMark, let completeAddress = completeAddress {
print(placeMark.postalCode)
print(placeMark)
print(completeAddress)
} else {
// do something with the error
}
Finaly the print:
15172
Calle del Arenal, 4, Calle del Arenal, 4, 15172 Oleiros, A Coruña, España # <+43.33190337,-8.37144380> +/- 100.00m, region CLCircularRegion (identifier:'<+43.33190337,-8.37144380> radius 70.84', center:<+43.33190337,-8.37144380>, radius:70.84m)
Calle del Arenal, 4, Oleiros, 15172, A Coruña, España
func convertLatLongToAddress(latitude:Double, longitude:Double) {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
var labelText = ""
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
if placeMark != nil {
if let name = placeMark.name {
labelText = name
}
if let subThoroughfare = placeMark.subThoroughfare {
if (subThoroughfare != placeMark.name) && (labelText != subThoroughfare) {
labelText = (labelText != "") ? labelText + "," + subThoroughfare : subThoroughfare
}
}
if let subLocality = placeMark.subLocality {
if (subLocality != placeMark.subThoroughfare) && (labelText != subLocality) {
labelText = (labelText != "") ? labelText + "," + subLocality : subLocality
}
}
if let street = placeMark.thoroughfare {
if (street != placeMark.subLocality) && (labelText != street) {
labelText = (labelText != "") ? labelText + "," + street : street
}
}
if let locality = placeMark.locality {
if (locality != placeMark.thoroughfare) && (labelText != locality) {
labelText = (labelText != "") ? labelText + "," + locality : locality
}
}
if let city = placeMark.subAdministrativeArea {
if (city != placeMark.locality) && (labelText != city) {
labelText = (labelText != "") ? labelText + "," + city : city
}
}
if let state = placeMark.postalAddress?.state {
if (state != placeMark.subAdministrativeArea) && (labelText != state) {
labelText = (labelText != "") ? labelText + "," + state : state
}
}
if let country = placeMark.country {
labelText = (labelText != "") ? labelText + "," + country : country
}
// labelText gives you the address of the place
}
})
}
Here as an improvement I added place name as well. It makes address more meaningful.
func getAddressFromlatLong(lat: Double, long: Double, completion: #escaping (_ address: String) -> Void){
let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
let geocoder = GMSGeocoder()
var add = ""
geocoder.reverseGeocodeCoordinate(coordinate) { (response, error) in
if let address = response?.firstResult() {
guard let arrAddress = address.lines else {return}
if arrAddress.count > 1 {
add = /(arrAddress[0]) + ", " + /(arrAddress[1])
}else if arrAddress.count == 1 {
add = /(arrAddress[0])
}
completion(add)
}
}
}