Store Location from API into Array

Im attempting to store the lat and lng values from a google maps api url into an array but when ever I try to run it, the array comes out blank. Im able to get the names into the array but not sure why I can't get lat and lng stored.



Answered by OOPer in 659659022

this is the first part of the output leading up to "results": 

Thanks. Generally, when you cannot get some item which surely exists in a JSON response, checking the whole response might be needed. Better remember the debugging code print(String(data: data, encoding: .utf8) ?? "?").

To summarize, the response outlined looks like this:
Code Block
{
"html_attributions" : [],
"next_page_token" : "...",
   "results" : [...],
   "status" : "OK"
}

It is a JSON object containing 4 entries, "html_attributions", "next_page_token", "results" and "status".
JSON object is represented as [String: Any] in Swift, or as NSDictionary in Objective-C.
So, your original code as? NSDictionary or my previous code as? [String: Any] does work.


And the value for "results" is a JSON array, which contains JSON objects.
Each element looks like this:
Code Block
{
"business_status" : "OPERATIONAL",
"geometry" : {
"location" : {
"lat" : 37.3514093,
"lng" : -122.0017734
},
"viewport" : {
"northeast" : {
"lat" : 37.35274472989272,
"lng" : -122.0004817701073
},
"southwest" : {
"lat" : 37.35004507010728,
"lng" : -122.0031814298927
}
}
},
"icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/restaurant-71.png",
"name" : "Roll N Noodle Food Court",
"opening_hours" : {
"open_now" : false
},
"photos" : [
{
"height" : 3024,
"html_attributions" : [
"\u003ca href=\"https://maps.google.com/maps/contrib/100046552616844334295\"\u003eA Google User\u003c/a\u003e"
],
"photo_reference" : "ATtYBwINYsbf5B1ox6_R1Du0EXIS3FxUl5Pg5W-ligjn41HHs14trlNGNpFey4vCGOmI_VWS1qT9F0Z3VjdXxvy3r8QYt42GtEBaAcoij9aRTzS6z0jnUkcuGrzi_AGixmDj_iiB-g3eYSZBCmbzDx1xJ3nmotgEW1INqY_Ddgi4PC4U78Ke",
"width" : 4032
}
],
"place_id" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"plus_code" : {
"compound_code" : "9X2X+H7 Sunnyvale, California",
"global_code" : "849V9X2X+H7"
},
"rating" : 4.9,
"reference" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"scope" : "GOOGLE",
"types" : [ "restaurant", "food", "point_of_interest", "establishment" ],
"user_ratings_total" : 13,
"vicinity" : "1092 E El Camino Real, Sunnyvale"
},

It can be outlines as follows:
Code Block
{
"business_status" : "OPERATIONAL",
"geometry" : {...},
"icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/restaurant-71.png",
"name" : "Roll N Noodle Food Court",
"opening_hours" : {...},
"photos" : [...],
"place_id" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"plus_code" : {...},
"rating" : 4.9,
"reference" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"scope" : "GOOGLE",
"types" : [ "restaurant", "food", "point_of_interest", "establishment" ],
"user_ratings_total" : 13,
"vicinity" : "1092 E El Camino Real, Sunnyvale"
},

There are several entries, but no entries for "location".
There is an entry for "name" and its value is a JSON string, so your result["name"] as? String does work.
But there aren't any entries for "location", result["location"] fails before evaluating as? String.


Assume you want to get the following info which is embedded in the value for "geometry":
Code Block
"location" : {
"lat" : 37.3514093,
"lng" : -122.0017734
},

You may need to dig into "geometry".

The value for "geometry" is a JSON object, so you need to write something like this:
Code Block
guard let geometry = result["geometry"] as? [String: Any] else {
print("value for `geometry` not found or not object")
return // or throw some error or ignore and continue
}

And then, the value for "location" is a JSON object:
Code Block
guard let location = geometry["location"] as? [String: Double] else {
print("value for `location` not found or not object")
return // or throw some error or ignore and continue
}

In "location", the values are all numbers, so [String: Double] is used instead of [String: Any].
Code Block
guard let lat = location["lat"],
let lng = location["lng"] else {
print("value for `lat` or `lng` not found or not number")
return // or throw some error or ignore and continue
}

With all these guard-lets, you can use lat and lng as Swift Double.

You may need to update your locationArray like this:
Code Block
    var locationArray: [CLLocationCoordinate2D] = []

And use it as:
Code Block
let coord = CLLocationCoordinate2D(latitude: lat, longitude: lng)
self.locationArray.append(coord)



The do-catch part would look like this with all above things included:



Generally, keeping two (or more) Arrays consistent would be difficult. So, having two Arrays separately like nameArray and locationArray is not considered to be a good practice. It may be too much to include into this thread.


Please try the new code above.

If you feel many guard-lets sort of annoying, you should better learn and try Swift Codable.
With more simple APIs...

the array comes out blank.

How have you checked it?


One more. Your code does not seem to be too long. Please use Code block (<>) instead of Text attachment.
The line with self.printNames() is a function that prints what ever is in the array out
Code Block var GoogleURLAPI = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(origin.latitude),\(origin.longitude)&radius=15000&type=Fast%20Food&keyword=Food&key=API KEY
GoogleURLAPI = GoogleURLAPI.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
var urlRequest = URLRequest(url: URL(string: GoogleURLAPI)!)
urlRequest.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error == nil
{//making sure it actually has something
let jsonDict = try? JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
//setting jsonDict to read datta from url
if let LocArray = jsonDict?.value(forKey: "results") as? NSArray
{
//print("or here",LocArray)
for x in LocArray
{
if let Location = x as? NSDictionary
{
if let loc = Location.value(forKey: "location")
{
self.tester.append(loc as! String)
//self.nameArray.append(loc as! String)
}
}
}
}
self.printNames()
//print("json = \(jsonDict)")
}
else
{
let alert = UIAlertController(title: "There was an Error", message: "We encountered an error while trying to connect to Google. Try again later.", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Okay!", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
task.resume()


Thanks for showing formatted code.

Yes, the line with self.printNames() is a funcition that prints what ever is in the array out

Sorry, but you have not shown the definition of printNames() and I cannot find what's going on without it.
This is the function. When on line 21, if let loc = Location.value(forKey: "location"), when the key is set to "name", the name array fills up with names from the google api, but when ever the value is set to location, it is always empty



Code Block
func printNames()
  {
    print("name array",nameArray)
    print("location array",locationArray)
  }


Thanks for showing the definition.

But in your shown code, self.nameArray.append(loc as! String) is commented out, so it does not make sense.

Anyway, your code is full of bad practices and you should better update it.
Please try the following code and tell us what you get from print statements.
Code Block
//#1 Start non-type identifiers with lower case letter
var googleURLAPI = URLComponents(string: "https://maps.googleapis.com/maps/api/place/nearbysearch/json")!
//#2 Better not call `addingPercentEncoding` yourself
googleURLAPI.queryItems = [
URLQueryItem(name: "location", value: "\(origin.latitude),\(origin.longitude)"),
URLQueryItem(name: "radius", value: "15000"),
URLQueryItem(name: "type", value: "Fast Food"),
URLQueryItem(name: "keyword", value: "Food"),
URLQueryItem(name: "key", value: "API KEY"),
]
print(googleURLAPI.url!)
var urlRequest = URLRequest(url: googleURLAPI.url!)
urlRequest.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: urlRequest) {
(data, response, error) in
do {
//making sure it actually has something
if let error = error {
throw error
}
//#3 Unwrap Optional safely with gurad-let
guard let data = data else {
print("data is nil")
return // or throw some error
}
//# For debugging, show response data as text
print(String(data: data, encoding: .utf8) ?? "?")
//#4 Better not use `try?`, use do-try-catch
//#5 `mutableContainers` has no meaning in Swift
//#6 Use Swift Dictionary type instead of `NSDictionary`
guard let jsonDict = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {
print("response data is not a JSON object")
return // or throw some error
}
//setting jsonDict to read datta from url
//#7 Use Swift Array type instead of `NSArray`
guard let results = jsonDict["results"] as? [[String: Any]] else {
print("`results` is not an Array of JSON object")
return // or throw some error
}
//print("or here",LocArray)
for result in results {
if let location = result["location"] as? String {
self.tester.append(location)
//self.nameArray.append(location)
} else {
//#8 Show debug info handling else-case
print("value for `location` not found or not string")
return // or throw some error
}
}
self.printNames()
//print("json = \(jsonDict)")
} catch {
print(error)
//#9 Better use `NSLocalizedString` for future internationalization
let title = NSLocalizedString("There was an Error", comment: "")
let message = NSLocalizedString("We encountered an error while trying to connect to Google. Try again later.", comment: "")
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
//#10 You can omit `handler:` when passing nil
alert.addAction(UIAlertAction(title: "Okay!", style: .default))
self.present(alert, animated: true, completion: nil)
}
}
task.resume()

Generally, your original code disposes error info and ignores many unexpected cases, and also using too many forced unwrappings (!) and forced-castings (as!). Have you picked up this code from a very old article?
The print statement on line 21 prints out the google api page and then nothing else is printed until line 51, "value for location not found or not string". I did try change the array type to double but same output. When I switch, on line 46, the key value to "name" it is still able to store the values of name into the array and prints out properly.

And yes, I'm attempting to teach myself how to program in swift and make an IOS app. I've been picking parts from different forums to try and make this app.

The print statement on line 21 prints out the google api page

There's no print statement on line 21 of my code, is it line 11?
And you mean the URL string by the google api page?

It would be far better copying all the outputs in the debug console than trying to describe them by words.

then nothing else is printed until line 51, "value for location not found or not string".

It is strange and would never happen. If line 51 would be executed, line 30 print(String(data: data, encoding: .utf8) ?? "?") is definitely executed.

 I did try change the array type to double but same output. 

Please show your code when talking about changing code. I really do not understand what you have tried.

When I switch, on line 46, the key value to "name" it is still able to store the values of name into the array and prints out properly.

I do not understand what you mean. What is switch? What is able to store the values?
Please show actual code you used and actual output you saw.

I've been picking parts from different forums to try and make this app.

Seems you have chosen a very bad one. If you find NS-something in a code, you should better avoid it.
(A little bit too simplified, but would be useful in many cases -- avoid NS-things.)

This is the entire printed console when the code is ran:





And what I mean with switch values, when I change
Code Block
if let location = result["location"] as? String
, to
Code Block
if let location = result["name"] as? String
, I am able to fill the name array and gets printed:

name array ["Roll N Noodle Food Court", "Jalisco Mexican Food", "True Food Kitchen", "Corazon Mexican Food", "Chick-fil-A", "Wendy\'s", "Jack in the Box", "Jack in the Box", "Chick-fil-A", "Jack in the Box", "Chick-fil-A", "Wendy\'s", "Wendy\'s", "Jora Peruvian food", "Jack in the Box", "Rubio\'s Coastal Grill", "sweetgreen", "Taqueria", "Arby\'s", "New Port Dim Sum and Chinese Food"]


This is the entire printed console when the code is ran: 

Thanks for showing the output, but the JSON, outermost structure is JSON object must start with { (opening brace).
Seems a few lines are missing.

Can you show really entire output?


(UPDATE)

Can you show really entire output?

Sorry, but no thank you.

Seems your Console Out contains outputs of two runs. The first 936 lines for the first run -- partial, and the latter for the second run -- entire.

Found what's wrong -- there's no entry of type String for the key "location" in the response.

Need some time to write a more detailed answer...
Sorry, this is the first part of the output leading up to "results":

{
  "htmlattributions" : [],
  "next
pagetoken" : "ATtYBwJIt2W3qkkzyErZBsrUXDJg8XW9cImoZT2uhayI9OdU-eLnyVdBfLZUHMyGRXnuLSxNyxDFDquPpf7lUqP0gm9I5Qvim1FaxE2uewpCTV3J37bMxTcR0sw1FdIviWQiGbelrUuduR3iaWgyDsw3dNM26XtHun3z2twzc32K24wXNXYjMsgJI1jUKRdy9Os4uyjjwm287ovoQdvqe-swRrvb1omAB-303ytyk7o0TmtohgT0-7c0pCODwqMMZ19R9ISvu6j0SkcTc9960XJriCPy7-BmTJNz8BFzLsS15dc0SVAEttjC9is5ZEjiw0Oa2-yzbfZbvCHNDH-v3HdjlYQLOD6YV4Oog0iVgQM4T_7yTao-LhEcrSeQGIcZlvbHHs5fzBJiNPmm7p7sVCpxoxes3wKO3rSTzAOEm-MRzyKetCUZV3ZxU1KqhoQ2Wdj2mo",
  "results" : [
   {
Accepted Answer

this is the first part of the output leading up to "results": 

Thanks. Generally, when you cannot get some item which surely exists in a JSON response, checking the whole response might be needed. Better remember the debugging code print(String(data: data, encoding: .utf8) ?? "?").

To summarize, the response outlined looks like this:
Code Block
{
"html_attributions" : [],
"next_page_token" : "...",
   "results" : [...],
   "status" : "OK"
}

It is a JSON object containing 4 entries, "html_attributions", "next_page_token", "results" and "status".
JSON object is represented as [String: Any] in Swift, or as NSDictionary in Objective-C.
So, your original code as? NSDictionary or my previous code as? [String: Any] does work.


And the value for "results" is a JSON array, which contains JSON objects.
Each element looks like this:
Code Block
{
"business_status" : "OPERATIONAL",
"geometry" : {
"location" : {
"lat" : 37.3514093,
"lng" : -122.0017734
},
"viewport" : {
"northeast" : {
"lat" : 37.35274472989272,
"lng" : -122.0004817701073
},
"southwest" : {
"lat" : 37.35004507010728,
"lng" : -122.0031814298927
}
}
},
"icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/restaurant-71.png",
"name" : "Roll N Noodle Food Court",
"opening_hours" : {
"open_now" : false
},
"photos" : [
{
"height" : 3024,
"html_attributions" : [
"\u003ca href=\"https://maps.google.com/maps/contrib/100046552616844334295\"\u003eA Google User\u003c/a\u003e"
],
"photo_reference" : "ATtYBwINYsbf5B1ox6_R1Du0EXIS3FxUl5Pg5W-ligjn41HHs14trlNGNpFey4vCGOmI_VWS1qT9F0Z3VjdXxvy3r8QYt42GtEBaAcoij9aRTzS6z0jnUkcuGrzi_AGixmDj_iiB-g3eYSZBCmbzDx1xJ3nmotgEW1INqY_Ddgi4PC4U78Ke",
"width" : 4032
}
],
"place_id" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"plus_code" : {
"compound_code" : "9X2X+H7 Sunnyvale, California",
"global_code" : "849V9X2X+H7"
},
"rating" : 4.9,
"reference" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"scope" : "GOOGLE",
"types" : [ "restaurant", "food", "point_of_interest", "establishment" ],
"user_ratings_total" : 13,
"vicinity" : "1092 E El Camino Real, Sunnyvale"
},

It can be outlines as follows:
Code Block
{
"business_status" : "OPERATIONAL",
"geometry" : {...},
"icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/v1/png_71/restaurant-71.png",
"name" : "Roll N Noodle Food Court",
"opening_hours" : {...},
"photos" : [...],
"place_id" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"plus_code" : {...},
"rating" : 4.9,
"reference" : "ChIJYya4KWe1j4ARw8nBr2iSdSI",
"scope" : "GOOGLE",
"types" : [ "restaurant", "food", "point_of_interest", "establishment" ],
"user_ratings_total" : 13,
"vicinity" : "1092 E El Camino Real, Sunnyvale"
},

There are several entries, but no entries for "location".
There is an entry for "name" and its value is a JSON string, so your result["name"] as? String does work.
But there aren't any entries for "location", result["location"] fails before evaluating as? String.


Assume you want to get the following info which is embedded in the value for "geometry":
Code Block
"location" : {
"lat" : 37.3514093,
"lng" : -122.0017734
},

You may need to dig into "geometry".

The value for "geometry" is a JSON object, so you need to write something like this:
Code Block
guard let geometry = result["geometry"] as? [String: Any] else {
print("value for `geometry` not found or not object")
return // or throw some error or ignore and continue
}

And then, the value for "location" is a JSON object:
Code Block
guard let location = geometry["location"] as? [String: Double] else {
print("value for `location` not found or not object")
return // or throw some error or ignore and continue
}

In "location", the values are all numbers, so [String: Double] is used instead of [String: Any].
Code Block
guard let lat = location["lat"],
let lng = location["lng"] else {
print("value for `lat` or `lng` not found or not number")
return // or throw some error or ignore and continue
}

With all these guard-lets, you can use lat and lng as Swift Double.

You may need to update your locationArray like this:
Code Block
    var locationArray: [CLLocationCoordinate2D] = []

And use it as:
Code Block
let coord = CLLocationCoordinate2D(latitude: lat, longitude: lng)
self.locationArray.append(coord)



The do-catch part would look like this with all above things included:



Generally, keeping two (or more) Arrays consistent would be difficult. So, having two Arrays separately like nameArray and locationArray is not considered to be a good practice. It may be too much to include into this thread.


Please try the new code above.

If you feel many guard-lets sort of annoying, you should better learn and try Swift Codable.
With more simple APIs...
Yes, that worked! But the array only shows it be populated after I run the function twice. Like, the first go through will print out two empty arrays and then when I tap the button to run the function again the array shows populated.

When the print statement is within the do brackets, it shows it is filled, but once its after the task.resume(), it will only show a populated array after the second execution

When the print statement is within the do brackets, it shows it is filled, but once its after the task.resume(), it will only show a populated array after the second execution 

You may be handling asynchronous method in a bad way.
Which may caused by your hidden parts of code and that is another issue.
You should better start a new thread which includes all relevant parts.


Please do not ask things which is not included in the opening post. Keep one thread for one topic.
Store Location from API into Array
 
 
Q