Unable Return String from CLGeocoder reverseGeocodeLocation - ios

I want to write a function to reverse geocode a location and assign the resulting string into a variable. Following this post i've got something like this:
extension CLLocation {
func reverseGeocodeLocation(completion: (answer: String?) -> Void) {
CLGeocoder().reverseGeocodeLocation(self) {
if let error = $1 {
print("[ERROR] \(error.localizedDescription)")
return
}
if let a = $0?.last {
guard let streetName = a.thoroughfare,
let postal = a.postalCode,
let city = a.locality else { return }
completion(answer: "[\(streetName), \(postal) \(city)]")
}
}
}
}
For calling this function i've just got something like this:
location.reverseGeocodeLocation { answer in
print(answer)
}
But instead i want to assign the string value of answer to a variable and i don't know how to pass that data out of the closure. What is the best way to do something like this?

The problem is that it runs asynchronously, so you can't return the value. If you want to update some property or variable, the right place to do that is in the closure you provide to the method, for example:
var geocodeString: String?
location.reverseGeocodeLocation { answer in
geocodeString = answer
// and trigger whatever UI or model update you want here
}
// but not here
The entire purpose of the closure completion handler pattern is that is the preferred way to provide the data that was retrieved asynchronously.

Short answer: You can't. That's not how async programming works. The function reverseGeocodeLocation returns immediately, before the answer is available. At some point in the future the geocode result becomes available, and when that happens the code in your closure gets called. THAT is when you do something with your answer. You could write the closure to install the answer in a label, update a table view, or some other behavior. (I don't remember if the geocoding methods' closures get called on the main thread or a background thread. If they get called on a background thread then you would need to wrap your UI calls in dispatch_async(dispatch_get_main_queue()).)

Related

How would I change the value of a UILabel from a different class in swift?

I am a student trying to make an app in Xcode, and I have run into an issue to which I cannot figure out the solution. I want to access a string I have stored in another class and make it the label text of my ViewController.
In my ViewController, I call a different class, which then calls through an API and finds me a string detailing the weather in a certain place. This is what my call looks like from my ViewController class
func getWeather() {
manager = DataManager()
manager.fetchData(query: getQueryString();
}
And this is what the call looks like from the DataManager Class
func rainyWeatherPopulate(str: String){
LoadEvents.loadEvents(type: EventType.rain, str: str)
}
in the loadEvents function, I get a string detailing the type of weather that I want to put as text of a UILabel in my ViewController class, and I am not sure how to do so
Is there any way that I can pass an instance of my ViewController to my loadEvents static function or any other way in which I can set the text of a UILabel in my ViewController from the LoadEvents class?
There are many potential ways to solve this problem, but one is by passing a closure to be used upon completion of the call.
So, it might look like this in your use case:
class DataManager {
func fetchData(query: String, onComplete: #escaping (String) -> Void) {
LoadEvents.loadEvents(type: .rain, str: query, onComplete: onComplete)
}
}
enum EventType {
case rain
}
class LoadEvents {
static func loadEvents(type: EventType, str: String, onComplete: (String) -> Void) {
//load the data
//then, when it's done, call the completion
onComplete("returnedData")
}
}
func getWeather() {
let manager = DataManager()
let uiLabel = UILabel()
manager.fetchData(query: "queryString", onComplete: { result in
uiLabel.text = result
})
}
I had to mock out some stuff (like EventType) and I had no information about the types of events you're loading, so I just used a generic String for the return type, but the concept is here.
How it works:
In getWeather, there's a closure called onComplete that will get data back once everything has completed. result holds (obviously) the result -- in this case a string, and it sets uiLabel's text property to that value.
LoadEvents does whatever it needs to and then upon finishing, calls onComplete and sends the results back through the closure.
I'd say jnpdx's solution of passing a closure to your data manager is the best answer.
Another way you could do this is to add a delegate property to your data manager. The only thing you know about the delegate is that it conforms to a specific protocol. You'd then invoke methods on the delegate from the data manager that would tell the delegate about data that was available for display. The delegate would decide what to do with that data.
I mention this as an alternative method, but would suggest using closures as jnpdx recommends in their answer.

Objective C: Unable to execute callback method in viewDidLoad function

I am new to iOS objective C and currently am enhancing some features to an existing project. I would like to implement a callback method to return the results from an API call in the viewDidLoad method as I need the results to determine the number of icons to display on the screen.
I have looked around for various answers, however, I am not sure what has went wrong and I am unable to return the responses from the APIs to the NSMutableArray in the ViewDidLoad method. I would greatly appreciate any help on how to go about get the response into the customerArray in the viewDidLoad method
This is the Service Delegate header file that defines the method:
I made use of the first method requestCompleted in my ViewController file to retrieve the response returned from the API. Within this method, customerArray which is a globally declared array contains the items that was returned from the API, however when this array was called in the viewDidLoad method, it appears to be null.
I tried the following way to retrieve using the dispatch async method, but the customer Array is still empty
You are printing customerArray, before dispatch_async executed. Try putting breakpoint in requestCompleted method or put print statement in requestCompleted method. and check the value.
You can't execute the callback method in viewDidLoad function. And then this attempt is not good pattern in iOS.
I understand what you want. You want to display various icons when view controller is appeared.
For this, you may need to use UITableView or CollectionView.
In this case, i have been doing following.
First, Run API call by using Alamofire
Second. Show loading icon (by using HUD)
Third, In API callback, fetch the list information and reload tableview.
*Following is swift code but it is very similar with Objective C.
override func viewDidLoad() {
super.viewDidLoad()
HUD.show(.rotatingImage(UIImage(named: "rotateLogo")), onView: nil)
APIManager.request(Constant.sdkCredential,
parameters: [:],
fullResponse:true)
{ (response, success, error) in
if success == true {
guard (response as? Dictionary<String,Any>) != nil else {
self.showAlert(withTitle: "Error", message: "Invalid Credential")
return
}
if let dict = response as? Dictionary<String,Any> {
print(dict)
if let dictData = dict["data"] as? Dictionary<String,Any>{
self.tableView.reloadData();
}
}
} else {
HUD.hide()
self.showAlertWithError(error)
}
}
}

Issue with global variables and Alamofire

So, I am trying to do a Alamofire request, then, I'd take the information I need from the JSON data and put it into a global variable, here's my code.
struct myVariables {
static var variableOne = ""
}
func function() {
Alamofire.request(.GET, "API URL").responseJSON { response in
if let rawJSON = response.result.value {
// Here I just take the JSON and put it into dictionaries and parse the data.
myVariables.variableOne = String("data")
}
}
}
Ok, so basically, I am trying to access variableOne's data from another Swift file. Let's say I made two Swift files and in one of those files I had a function that edited the value of global variable, in the other file, if I attempted to print that global variable, I'd see the edited value. But whenever I use Alamofire, when I try to edit a global variable, the other Swift file doesn't see the changed value. So if I tried to edit the global variable within the Alamofire request block of code, I don't see the change whenever I print the variable from another file.
If anyone knows a better way to phrase that, please do correct it.
I suspect the problem isn't that you're not seeing the value change, but rather an issue arising from the fact that you're dealing with an asynchronous method. For example, when you call function, it returns immediately, but your variableOne may not be updated immediately, but rather later. I bet you're checking it before this asynchronous response closure had a chance to be called.
You wouldn't have this problem if, rather than using this "global" (which is a bad idea, anyway), you instead adopted the completion handler pattern yourself.
func performRequest(completionHandler: #escaping (String?, Error?) -> Void) {
Alamofire.request("API URL").responseJSON { response in
switch response.result {
case .failure(let error):
completionHandler(nil, error)
case .success(let responseObject):
let dictionary = responseObject as? [String: Any]
let string = dictionary?["someKey"] as? String
completionHandler(string, nil)
}
}
}
An you'd call this like so:
performRequest() { string, error in
guard let string = string, error == nil else {
// handle error here
return
}
// use `string` here
}
// but not here, because the above closure runs asynchronously (i.e. later)
By using this completion handler pattern, we solve the "how do I know when the asynchronous method is done" problem. And by passing the necessary data back as a parameter of the closure, we can excise the use of globals, keeping the scope of our data as narrow as possible.
Clearly, you should change the parameter of the closure to match whatever is appropriate in your case. But hopefully this illustrates the basic idea.
See previous revision of this answer for Swift 2/Alamofire 3 answer.

How to set a Inout parameter inside a closure

So my question is this:
I'm trying to implement a extension to CLGeoCoder to be able to get a city name from a CLLocation but I'm facing a problem when I try to set the inout city name string inside the completionhandler of the reverseGeocodeLocation
Here is my code for my extension:
extension CLGeocoder {
func findCityName(location:CLLocation, inout cityName:String) -> Void{
self.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// Address dictionary
print(placeMark.addressDictionary)
// City
if let city = placeMark.addressDictionary!["City"] as? String {
cityName = city
}
})
}
}
The goal of this extension is to be able to set a string with a city name by passing it by reference to this function.
An example would be:
var cityName = ""
let location:CLLocation(latitude: 1, longitude: 1)
let geocoder: CLGeocoder()
geocoder.findCityName(location, &cityName)
///Later on in the process I would want to be able to use cityName for different purposes in my code
The problem I'm facing is that after setting the cityName reference in the completionHandler, the value of cityName stays empty outside of the completionhandler.
I'm new to swift so I've maybe missed something on pointers or how to use them but I had the understanding that inout would make it possible to change the value of a variable from inside a function or a closure/completionhandler.
Thank you in advance for any information you could give me on this problem I'm facing.
Martin
At the time your findCityName function exits, the cityName variable has not been changed. The completion handler of reverseGeocodeLocation will eventually update a variable that is a copy of the original cityName, not the variable that you originally passed to the function. Completion handlers don't execute right away: they will execute. If they would execute right away, functions could just return their results instead of using completion handlers, right?
Now, I agree with you, this is odd. But what is odd is not that Swift does not update your inout variable. What is odd is that Swift lets this program compile. Because it has no meaning. Follow me:
Imagine the following function:
func f() {
var cityName: String = ""
findCityName(location, &cityName)
}
When you call it, the cityName variable you passed in no longer exists when the completion handler of reverseGeocodeLocation eventually executes. The f() function has long exited, and the local variable cityName has vanished out of existence.
Got it? It's meaningless to even hope that the inout variable could be updated.
So. Now, you have to refactor your code. Maybe use a completion handler.

Capturing closure values in Swift

My question is very similar to several others here but I just can't get it to work. I'm making an API call via a helper class that I wrote.
First I tried a standard function with a return value and the result was as expected. The background task completed after I tired to assign the result.
Now I'm using a closure and I can get the value back into my view controller but its still stuck in the closure, I have the same problem. I know I need to use GCD to get the assignment to happen in the main queue.
this is what I have in my view controller
var artists = [String]()
let api = APIController()
api.getArtistList("foo fighters") { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
print("in the closure: \(artists)")
}
}
}
print ("method 1 results: \(artists)")
as the results are:
method 1 results: []
in the closure: [Foo Fighters & Brian May, UK Foo Fighters, John Fogerty with Foo Fighters, Foo Fighters, Foo Fighters feat. Norah Jones, Foo Fighters feat. Brian May, Foo Fighters vs. Beastie Boys]
I know why this is happening, I just don't know how to fix it :( The API calls need to be async, so what is the best practice for capturing these results? Based on what the user selects in the table view I'll be making subsequent api calls so its not like I can handle everything inside the closure
I completely agree with the #Craig proposal of the use of the GCD, but as your question involves the request of the API call every time you select a row, you can do the following:
Let's suppose you use the tableView:didSelectRowAtIndexPath: method to handle the selection, then you can do the following inside it:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// it is just a form to get the item
let selectedItem = items.objectAtIndex(indexPath.row) as String
api.getArtistList(selectedItem) { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
}
}
}
}
And then you can observe the property and handle do you want inside it :
var artists: [String] = [] {
didSet {
self.tableView.reloadData() // or anything you need to handle.
}
}
It just another way to see it. I hope this help you.
The easy solution is to do whatever you're doing at your print(), inside the closure.
Since you're already dispatch_asyncing to the main queue (the main/GUI thread), you can complete any processing there. Push a new view controller, present some modal data, update your current view controller, etc.
Just make sure that you don't have multiple threads modifying/accessing your local/cached data that is being displayed. Especially if it's being used by UITableViewDelegate / UITableViewDataSource implementations, which will throw fits if you start getting wishy-washy or inconsistent with your return values.
As long as you can retrieve the data in the background, and the only processing that needs to occur on the main thread is an instance variable reassignment, or some kind of array appending, just do that on the main thread, using the data you retrieved on the back end. It's not heavy. If it is heavy, then you're going to need more sophisticated synchronization methods to protect your data.
Normally the pattern looks like:
dispatch_async(getBackgroundQueue(), {
var theData = getTheDataFromNetwork();
dispatch_async(dispatch_get_main_queue() {
self.data = theData // Update the instance variable of your ViewController
self.tableView.reloadData() // Or some other 'reload' method
});
})
So where you'd normally refresh a table view or notify your ViewController that the operation has completed (or that local data has been updated), you should continue your main-thread processing.

Resources