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.
Related
I try use nested DispatchGroup, there is my code :
class TranslateService {
private let myGroup = DispatchGroup()
func translateText(text:[String],closure:#escaping ((_ success:String?,_ error:Error?) -> Void)) {
var translateString: String = ""
var responseError: Error?
for index in 0...text.count - 1 {
let urlString = "https://translate.yandex.net/api/v1.5/tr.json/translate?key=trnsl.1.1.20171105T134956Z.795c7a0141d3061b.dc25bae76fa5740b2cdecb02396644dea58edd24&text=\(text[index])&lang=fa&format=plain&options=1"
if let allowString = Utilities.shareInstance.getQueryAllowedString(url: urlString) {
if let url = URL(string:allowString){
myGroup.enter()
Alamofire.request(url).responseJSON { response in
guard let responseData = response.data else {
self.myGroup.leave()
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]
}
}
}
}
self.myGroup.leave()
}
}catch {
responseError = error
self.myGroup.leave()
}
}
self.myGroup.notify(queue: .main) {
print("Finished all requests.")
print(translateString)
closure(translateString, responseError)
}
}
}
}
}
}
class AddressService {
private let translateService: TranslateService = TranslateService()
private let myGroup = DispatchGroup()
func fetchAddressFromGeographicalLocation(latitude: Double, longitude: Double,closure:#escaping ((_ success:String,_ name:String,_ error:Error?) -> Void)) {
var address: String = ""
let name: String = ""
var responseError: Error?
if let url = URL(string:"https://maps.googleapis.com/maps/api/geocode/json?latlng=\(latitude),\(longitude)&key=AIzaSyAdEzHZfZWyjLMuuW92w5fkR86S3-opIF0&language=fa®ion=IR&locale=fa"){
self.myGroup.enter()
Alamofire.request(url).responseJSON { response in
guard let responseData = response.data else {
self.myGroup.leave()
return
}
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: [])
if let addressDic = json as? [String:Any] {
if let result = addressDic["results"] as? [AnyObject] {
if result.count > 0 {
let flattRes = Utilities.shareInstance.flatMapArray(result)
let item = flattRes[0]
address = item["formatted_address"] as? String ?? ""
var res = address
if res.isContainEnglishCharachter {
self.myGroup.enter()
let resArr = res.components(separatedBy: ",")
var all : [String] = []
for item in resArr {
if item != " " {
all.append(item)
}}
self.translateService.translateText(text: all, closure: {stringAdd,error in
self.myGroup.enter()
if error != nil {
self.myGroup.leave()
}else {
address = stringAdd ?? ""
self.myGroup.leave()
} })
}else {
self.myGroup.leave()
}
}
}
}
}catch {
responseError = error
self.myGroup.leave()
}
self.myGroup.notify(queue: .main) {
// print("Finished all requests.")
closure(address, name, responseError)
}
}
}
}
}
All I want is that the myGroup I put in the class AddressService waiting for the myGroup that I put in the class TranslateService.
but now self.myGroup.notify not call in the AddressService class, So closure not work.
How can solve this problem, Thank you for all the answers.
I think you are over complicating it.
If I understood a bit, what you want to do is the following:
Get an address from the Address service.
Translate some words of that address, one by one, using the translation service.
When using the Address service there is only one call being done, so there is no need to use Dispatch Groups at this point. Dispatch Groups are used to synchronize more than one async call.
For your Translation service you can make good use of the Dispatch groups, since you are doing calls to the service inside a for loop. The problem here is, that the implementation is slightly wrong. You are setting the notification block inside the for loop, and it should be outside, so that it gets only triggered once, when all the calls inside the loop are done.
So move this block outside the for loop in the Translation service:
self.myGroup.notify(queue: .main) {
print("Finished all requests.")
print(translateString)
closure(translateString, responseError)
}
Now "Finished all requests." will only be printed once, when all requests are done.
In the address service you do not need dispatch groups at all. Just wait until the completion block is called.
self.translateService.translateText(text: all, closure: {stringAdd,error in
Everything is done here already.
}
Newbie here.Using Google API Nearby Search. I have problem sending encapsulated data into closure, already populated table with vicinity info, but when i try to send placeID info into closure to get Details, it gives me nil.
Here i get placeID and vicinity, and afterwards populate tableView with places array. Class Place is in separate swift file, function downloadPlaceID is inside ViewController.
class Place {
var placeId: String!
var vicinity: String!
var _placeId: String {
if placeId == nil {
placeId = ""
}
return placeId
}
var _vicinity: String {
if vicinity == nil {
vicinity = ""
}
return vicinity
}
init( place: [String:Any]) {
if let ids = place["id"] as? String {
self.placeId = ids
}
if let vicinities = place["vicinity"] as? String {
self.vicinity = vicinities
}
}
}
func downloadPlaceID (completed: #escaping DownloadComplete) {
let placeURL = URL(string: nearbyURL)
Alamofire.request(placeURL!).responseJSON { (response) in
let result = response.result
if let dictionary = result.value as? [String:Any] {
if let results = dictionary["results"] as? [[String:Any]] {
if let status = dictionary["status"] as? String {
if status == "OK" {
for obj in results {
place = Place(place: obj)
// here i get all the placeID's
places.append(place)
}
}
}
}
}
completed()
}
}
Then i try to get details, into which I put placeID:
func downloadDetails( input: String, completed: DownloadComplete) {
let details = "\(detailsBaseURL)\(detailsPlaceId)\(input)\(detailsKey)\(detailsSearchAPIKey)"
print(placeID)
Alamofire.request(details).responseJSON { response in
let result = response.result
if let dictionary = result.value as? [String:Any] {
if let result = dictionary["result"] as? [String:Any] {
if let phoneNumber = result["formatted_phone_number"] as? String {
self.phone = phoneNumber
print(self.phone!)
}
if let geometry = result["geometry"] as? [String:Any] {
if let location = geometry["location"] as? [String:Any] {
if let latitude = location["lat"] as? Double {
self.lat = latitude
print(self.lat!)
}
if let longitude = location["lng"] as? Double {
self.lng = longitude
print(self.lng!)
}
}
}
if let openingHours = result["opening_hours"] as? [String:Any] {
if let openNow = openingHours["open_now"] as? Bool {
self.workHours = openNow
print(self.workHours!)
}
}
}
}
}
}
Here is code inside viewDidLoad that i'm trying to use to get details.
override func viewDidLoad() {
super.viewDidLoad()
downloadPlaceID {
detail.downloadDetails(input: place.placeId, completed: {
})
}
}
It should be "place_id" instead of "id"
class Place {
var placeId: String!
var vicinity: String!
var _placeId: String {
if placeId == nil {
placeId = ""
}
return placeId
}
var _vicinity: String {
if vicinity == nil {
vicinity = ""
}
return vicinity
}
init( place: [String:Any]) {
if let ids = place["place_id"] as? String {
self.placeId = ids
}
if let vicinities = place["vicinity"] as? String {
self.vicinity = vicinities
}
}
}
I called data from the web for infinite-scrolling and "push to refresh", now infinite-scrolling work well, but "Pull to refresh" is not work because I do not know what kind of code I have to write in refreshAction func.
NetworkRequestAPI Request
import Foundation
class NetworkRequestAPI {
static func getPropductListByCategory(productId : Int, pageNo : Int , completion: #escaping ([Product]? , Error?) -> ()){
let url = URL(string: Configuration.BASE_URL+"/product-by/type?product_type_id="+String(productId)+"&page="+String(pageNo))
var categoryObject = [Product]()
URLSession.shared.dataTask(with:url!) { (urlContent, response, error) in
if error != nil {
}
else {
do {
let json = try JSONSerialization.jsonObject(with: urlContent!) as! [String:Any]
let products = json["products"] as? [String: Any]
// productCount = (json["product_count"] as? Int)!
let items = products?["data"] as? [[String:Any]]
items?.forEach { item in
let oProduct = Product()
oProduct.product_id = item["product_id"] as? Int
oProduct.product_name = item["product_name"] as? String
oProduct.product_image = item["product_image"] as? String
let ratingItem = item["rating_info"] as? [String: AnyObject]
let rating = RatingInfo()
rating.final_rating = ratingItem?["final_rating"] as? String
oProduct.rating_info = rating
categoryObject.append(oProduct)
}
completion(categoryObject, nil)
} catch let error as NSError {
print(error)
completion(nil, error)
}
}
}.resume()
}
}
ListTableView Class
class ListTableView: UITableViewController {
var isInitUILoad = true
var arrProduct = [[Product]]()
var product_id:Int = 0
var pageNo = 1
override func viewDidLoad() {
super.viewDidLoad()
self.initUILoad()
let refreshControl = UIRefreshControl()
if #available(iOS 10.0, *) {
tableView.refreshControl = refreshControl
} else {
tableView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshWeatherData(_:)), for: .valueChanged)
}
#objc private func refreshWeatherData(_ sender: Any) {
//
// what codes have to add here
//
self.refreshControl?.endRefreshing()
}
func initUILoad(){
ActivityIndicator.customActivityIndicatory(self.view, startAnimate: true)
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: pageNo) { (products, error) in
DispatchQueue.main.async(execute: {
if products != nil {
// self.totalItemLabel.text = String(self.product_count) + " products"
self.pageNo += 1
// self.product_count = productCount
//self.arrProduct = products!
self.arrProduct.append(products!)
print(self.arrProduct.count)
// self.tableView?.reloadData()
}else{
print(error.debugDescription)
}
ActivityIndicator.customActivityIndicatory(self.view, startAnimate: false)
// self.totalItemLabel.text = String(self.product_count) + " products"
self.tableView?.reloadData()
self.isInitUILoad = false
})
}
}
func loadMore(complition:#escaping (Bool) -> ()) {
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: pageNo) { (products, error) in
DispatchQueue.main.async(execute: {
if error != nil{
print(error.debugDescription)
}
if products != nil && products?.count ?? 0 > 0{
// self.totalItemLabel.text = String(self.product_count) + " products"
self.pageNo += 1
// self.product_count = productCount
// self.arrProduct = products!
self.arrProduct.append(products!)
self.tableView?.insertSections([self.arrProduct.count - 1], with: .fade)
// self.tableView?.reloadData()
}else{
print("no product left")
}
complition(true)
})
}
}
}
#objc private func refreshWeatherData(_ sender: Any) {
NetworkRequestAPI.getPropductListByCategory(productId: product_id, pageNo: 1) { (products, error) in
DispatchQueue.main.async(execute: {
if products != nil {
// self.totalItemLabel.text = String(self.product_count) + " products"
self.arrProduct = products!
print(self.arrProduct.count)
}else{
print(error.debugDescription)
}
self.refreshControl?.endRefreshing()
// self.totalItemLabel.text = String(self.product_count) + " products"
self.tableView?.reloadData()
self.isInitUILoad = false
})
}
}
I'm trying to loop through an embedded JSON array and extract all the values to put in a local array. This is what the JSON looks like:
"welcome": {
"data": {
"tncUrl": ""
},
"items": [
{
"newUser": [
{
"stepConcept": false
},
{
"stepSafety": true
},
{
"stepFacilitator": true
},
{
"stepTransparency": true
}
],
"switcher": [
{
"stepConcept": true
},
{
"stepSafety": true
},
{
"stepFacilitator": true
},
{
"stepTransparency": true
}
]
}
]
}
I'm able to get to a point where I can see I'm retrieving values for "newUser", the problem is looping through those values and adding them to an array. I'm getting a EXC_BAD_INSTRUCTION error when doing so. This is the code I'm using to get those values:
func prepareArrayOfViews(userType: User)
{
if (welcomeJSON != nil)
{
let items : NSArray? = welcomeJSON!.value(forKey: "items") as? NSArray
if (items == nil)
{
listOfViews = ["stepConcept", "stepSafety", "stepFacilitator", "stepTransparency"]
maxPages = listOfViews.count
return
}
if (items != nil) {
if let newUser = (items?.value(forKey: "newUser") as? NSArray){
//Below is where the error "EXC_BAD_INSTRUCTION"
for key in (newUser as! NSDictionary).allKeys
{
if (((newUser as! NSDictionary).value(forKey: key as! String) as? Bool)!)
{
listOfViews.append(key as! String)
}
}
}
if (listOfViews.count == 0)
{
listOfViews = ["stepConcept", "stepSafety", "stepFacilitator", "stepTransparency"]
}
maxPages = listOfViews.count
}
}
}
I have changed your code to use native Swift structs. Since you are not handling errors or doing anything when your optional unwrapping doesn't work, I have also changed the unwrapping to guard statements.
Other than the serious problems with Swift coding practices, your issue was that you were trying to iterate through an array of dictionaries as a simple dictionary.
func prepareArrayOfViews(userType: User){
guard let welcomeJSON = welcomeJSON else {return}
guard let items = welcomeJSON["items"] as? [[String:Any]] else {
listOfViews = ["stepConcept", "stepSafety", "stepFacilitator", "stepTransparency"]
maxPages = listOfViews.count
return
}
for item in items {
if let newUser = item["newUser"] as? [[String:Any]] {
for embeddedDict in newUser {
for (key, value) in embeddedDict {
if let val = value as? Bool, val == true {
listOfViews.append(key)
}
}
}
} else if let switcher = item["switcher"] as? [[String:Any]]{
for embeddedDict in switcher {
for (key, value) in embeddedDict {
if let val = value as? Bool, val == true {
//do whatever you need to with the value
}
}
}
}
}
if (listOfViews.count == 0){
listOfViews = ["stepConcept", "stepSafety", "stepFacilitator", "stepTransparency"]
}
maxPages = listOfViews.count
}
Because
//here newUser is an NSArray
if let newUser = (items?.value(forKey: "newUser") as? NSArray){
//here newUser forced to NSDictionary
for key in (newUser as! NSDictionary).allKeys
try to change this part to
if let newUsers = (items?.value(forKey: "newUser") as? NSArray){
for newUser in newUsers
{
for key in (newUser as! NSDictionary).allKeys
{
if (((newUser as! NSDictionary).value(forKey: key as! String) as? Bool)!)
{
listOfViews.append(key as! String)
}
}
}
}
I have an iOS swift app using Firebase realtime database. If I use the app normally so far I cannot find any issue. However, I want to anticipate edge cases.
I am trying to stress test my app before I push the update, and one way I am doing it is quickly going back and forth from a VC with a tableView to the next VC which is a detail VC. If I do it several times eventually the tableview will show lots of duplicate data.
I have tested my app by having a tableview open on my simulator and going into my Firebase Console and manually changing a value and instantly on the device the string changes.
So I am confused as to why my tableview would show an incorrect amount of children if it is constantly checking what the value should be.
// MARK: Firebase Methods
func checkIfDataExits() {
DispatchQueue.main.async {
self.cardArray.removeAll()
self.ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.hasChild("cards") {
self.pullAllUsersCards()
} else {
self.tableView.reloadData()
}
})
}
}
func pullAllUsersCards() {
cardArray.removeAll()
let userRef = ref.child("users").child((user?.uid)!).child("cards")
userRef.observe(DataEventType.value, with: { (snapshot) in
for userscard in snapshot.children {
let cardID = (userscard as AnyObject).key as String
let cardRef = self.ref.child("cards").child(cardID)
cardRef.observe(DataEventType.value, with: { (cardSnapShot) in
let cardSnap = cardSnapShot as DataSnapshot
let cardDict = cardSnap.value as! [String: AnyObject]
let cardNickname = cardDict["nickname"]
let cardType = cardDict["type"]
let cardStatus = cardDict["cardStatus"]
self.cardNicknameToTransfer = cardNickname as! String
self.cardtypeToTransfer = cardType as! String
let aCard = CardClass()
aCard.cardID = cardID
aCard.nickname = cardNickname as! String
aCard.type = cardType as! String
aCard.cStatus = cardStatus as! Bool
self.cardArray.append(aCard)
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
})
}
I got help and changed my code drastically, so now it works
func checkIfDataExits() {
self.ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.hasChild("services") {
self.pullCardData()
} else {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
func pullCardData() {
let cardRef = self.ref.child("cards")
cardRef.observe(DataEventType.value, with: { (snapshot) in
for cards in snapshot.children {
let allCardIDs = (cards as AnyObject).key as String
if allCardIDs == self.cardID {
if let childId = self.cardID {
let thisCardLocation = cardRef.child(childId)
thisCardLocation.observe(DataEventType.value, with: { (snapshot) in
let thisCardDetails = snapshot as DataSnapshot
if let cardDict = thisCardDetails.value as? [String: AnyObject] {
self.selectedCard?.cardID = thisCardDetails.key
self.selectedCard?.nickname = cardDict["nickname"] as? String ?? ""
self.selectedCard?.type = cardDict["type"] as? String ?? ""
self.pullServicesForCard()
}
})
}
}
}
})
}
func pullServicesForCard() {
if let theId = self.cardID {
let thisCardServices = self.ref.child("cards").child(theId).child("services")
thisCardServices.observe(DataEventType.value, with: { (serviceSnap) in
if self.serviceArray.count != Int(serviceSnap.childrenCount) {
self.serviceArray.removeAll()
self.fetchAndAddAllServices(serviceSnap: serviceSnap, index: 0, completion: { (success) in
if success {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
})
}
}
func fetchAndAddAllServices(serviceSnap: DataSnapshot, index: Int, completion: #escaping (_ success: Bool) -> Void) {
if serviceSnap.hasChildren() {
if index < serviceSnap.children.allObjects.count {
let serviceChild = serviceSnap.children.allObjects[index]
let serviceID = (serviceChild as AnyObject).key as String
let thisServiceLocationInServiceNode = self.ref.child("services").child(serviceID)
thisServiceLocationInServiceNode.observeSingleEvent(of: DataEventType.value, with: { (thisSnap) in
let serv = thisSnap as DataSnapshot
if let serviceDict = serv.value as? [String: AnyObject] {
let aService = ServiceClass(serviceDict: serviceDict)
self.serviceCurrent = serviceDict["serviceStatus"] as? Bool
self.serviceName = serviceDict["serviceName"] as? String ?? ""
self.serviceURL = serviceDict["serviceURL"] as? String ?? ""
self.serviceFixedBool = serviceDict["serviceFixed"] as? Bool
self.serviceFixedAmount = serviceDict["serviceAmount"] as? String ?? ""
self.attentionInt = serviceDict["attentionInt"] as? Int
self.totalArr.append((serviceDict["serviceAmount"] as? String)!)
// self.doubleArray = self.totalArr.flatMap{ Double($0) }
// let arraySum = self.doubleArray.reduce(0, +)
// self.title = self.selectedCard?.nickname ?? ""
// if let titleName = self.selectedCard?.nickname {
// self.title = "\(titleName): \(arraySum)"
// }
aService.serviceID = serviceID
if serviceDict["serviceStatus"] as? Bool == true {
self.selectedCard?.cStatus = true
} else {
self.selectedCard?.cStatus = false
}
if !self.serviceArray.contains(where: { (service) -> Bool in
return service.serviceID == aService.serviceID
}) {
self.serviceArray.append(aService)
self.serviceArray.sort {$1.serviceAttention < $0.serviceAttention}
}
}
self.fetchAndAddAllServices(serviceSnap: serviceSnap, index: index + 1, completion: completion)
})
}
else {
completion(true)
}
}
else {
completion(false)
}
}