Reputation: 269
Im creating an app that needs to be able to determine what locations are in a given area and then retrieve details about those locations, which then will be displayed. To do this I've been using Google Places API, but I am having trouble retrieving place details for locations using their place_id.
At the moment I am able to successfully perform a Google Places 'Nearby Search' request which returns many locations and some basic data about them (including a place_id). What I'm having trouble with is using the place-id's I receive from the first request to make a 'Place Details' request to get all the information Google has about the respective location.
vv My code below details my current attempt vv
function where I call the 2 requests:
func requestAndCombineGData(location: CLLocation, radius: Int) {
self.mapView.clear()
// Calls 'Nearby Search' request
googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
print("Made Nearby Search request. Passed response here:", response)
// loops through each result from the 'Nearby Request' to get the 'place_id' and make 'Place Details'
for location in response.results {
// Calls 'Place Details' request
self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
print("Made Place Details request. Passed response here:", detailsResponse)
// function to drop markers using data received above here
// self.putPlaces(places: response.results)
}
}
}
}
GoogleClient.swift file that contains the code for handling the requests above:
import SwiftUI
import Foundation
import CoreLocation
//Protocol
protocol GoogleClientRequest {
var googlePlacesKey : String { get set }
func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ())
func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ())
}
// GoogleClient class that conforms to the ZGoogleClient Request protocol
class GoogleClient: GoogleClientRequest {
let session = URLSession(configuration: .default)
var googlePlacesKey: String = "MY_KEY_GOES_HERE"
let categoriesArray = [
"park",
"restaurant",
"zoo"
]
func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ()) {
for category in categoriesArray {
let url = googlePlacesNearbyDataURL(forKey: googlePlacesKey, location: location, radius: radius, type: category)
let task = session.dataTask(with: url) { (responseData, _, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = responseData, let response = try? JSONDecoder().decode(GooglePlacesResponse.self, from: data) else {
print("Could not decode JSON response")
completionHandler(GooglePlacesResponse(results:[]))
return
}
if response.results.isEmpty {
print("GC - response returned empty", response)
} else {
print("GC - response contained content", response)
completionHandler(response)
}
}
task.resume()
}
}
func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ()) {
let url = googlePlacesDetailsURL(forKey: googlePlacesKey, place_ID: place_id)
let task = session.dataTask(with: url) { (responseData, _, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = responseData, let detailsResponse = try? JSONDecoder().decode(GooglePlacesDetailsResponse.self, from: data) else {
print("Could not decode JSON response. responseData was: ", responseData)
completionHandler(GooglePlacesDetailsResponse(results:[]))
return
}
// print("response result: ", detailsResponse.results)
if detailsResponse.results.isEmpty {
print("getGPDetails - response returned empty", detailsResponse)
} else {
print("getGPDetails - response contained content", detailsResponse)
completionHandler(detailsResponse)
}
}
task.resume()
}
func googlePlacesNearbyDataURL(forKey apiKey: String, location: CLLocation, radius: Int, type: String) -> URL {
print("passed location before url creation ", location)
print("passed radius before url creation ", radius)
let baseURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
let locationString = "location=" + String(location.coordinate.latitude) + "," + String(location.coordinate.longitude)
let radiusString = "radius=" + String(radius)
let typeString = "type=" + String(type)
// let rankby = "rankby=distance"
// let keywrd = "keyword=" + keyword
let key = "key=" + apiKey
print("Request URL:", URL(string: baseURL + locationString + "&" + radiusString + "&" + type + "&" + key)!)
return URL(string: baseURL + locationString + "&" + radiusString + "&" + typeString + "&" + key)!
}
func googlePlacesDetailsURL(forKey apiKey: String, place_ID: String) -> URL {
print("passed place_ID before url creation ", place_ID)
let baseURL = "https://maps.googleapis.com/maps/api/place/details/json?"
let place_idString = "place_id=" + place_ID
let fields = "fields=rating"
let key = "key=" + apiKey
print("Details request URL:", URL(string: baseURL + place_idString + "&" + fields + "&" + key)!)
return URL(string: baseURL + place_idString + "&" + fields + "&" + key)!
}
}
ResponseModels.swift file that holds the structs to handle the 2 different request responses:
import SwiftUI
import Foundation
struct GooglePlacesResponse : Codable {
let results : [Place]
enum CodingKeys : String, CodingKey {
case results = "results"
}
}
// Place struct
struct Place : Codable {
let geometry : Location
let name : String
let place_id: String
let openingHours : OpenNow?
let photos : [PhotoInfo]?
let types : [String]
let address : String
enum CodingKeys : String, CodingKey {
case geometry = "geometry"
case name = "name"
case place_id = "place_id"
case openingHours = "opening_hours"
case photos = "photos"
case types = "types"
case address = "vicinity"
}
// Location struct
struct Location : Codable {
let location : LatLong
enum CodingKeys : String, CodingKey {
case location = "location"
}
// LatLong struct
struct LatLong : Codable {
let latitude : Double
let longitude : Double
enum CodingKeys : String, CodingKey {
case latitude = "lat"
case longitude = "lng"
}
}
}
// OpenNow struct
struct OpenNow : Codable {
let isOpen : Bool
enum CodingKeys : String, CodingKey {
case isOpen = "open_now"
}
}
// PhotoInfo struct
struct PhotoInfo : Codable {
let height : Int
let width : Int
let photoReference : String
enum CodingKeys : String, CodingKey {
case height = "height"
case width = "width"
case photoReference = "photo_reference"
}
}
}
struct GooglePlacesDetailsResponse : Codable {
let results : [PlaceDetails]
enum CodingKeysDetails : String, CodingKey {
case results = "results"
}
}
// PlaceDetails struct
// I have fields commented out because I wanted to just get a location's rating for testing before implementing the rest
struct PlaceDetails : Codable {
// let place_id: String
// let geometry : Location
// let name : String
let rating : CGFloat?
// let price_level : Int
// let types : [String]
// let openingHours : OpenNow?
// let formatted_address : String
// let formatted_phone_number : String
// let website : String
// let reviews : String
// let photos : [PhotoInfo]?
enum CodingKeysDetails : String, CodingKey {
// case place_id = "place_id"
// case geometry = "geometry"
// case name = "name"
case rating = "rating"
// case price_level = "price_level"
// case types = "types"
// case openingHours = "opening_hours"
// case formatted_address = "formatted_address"
// case formatted_phone_number = "formatted_phone_number"
// case website = "website"
// case reviews = "reviews"
// case photos = "photos"
}
}
Google Places API documentation I've been referencing:
https://developers.google.com/places/web-service/search
-
Like I said above, right now my first request ('Nearby Search') is successfully returning data, but when I try to implement the second request ('Place Details') using a 'place_id'. My console keeps returning "Could not decode JSON response. Response: Optional(106 bytes)" which comes from a comment in my 'GoogleClient.swift' file.
My questions are:
What is wrong with how I'm making the 'Place Details' request that is causing it to return that the response cant be decoded?
AND
Is there a better way to make a Nearby Search request then use the 'place_id' from the returned locations to make a Place Details request in swift?
Upvotes: 0
Views: 2769
Reputation: 56
It looks like the issue you're having is with your model:
struct GooglePlacesDetailsResponse : Codable {
let results : [PlaceDetails]
enum CodingKeysDetails : String, CodingKey {
case results = "results"
}
}
According to the documentation there is no key on the place details response for "results" the correct key is "result" non-plural. Also you define the model as an array of the struct PlaceDetails, which is incorrect. When you return place details using a place id it will return only one object, which is the place details for that specific id as the place id is a unique value. I expect if you correct your response model your issues should resolve.
Upvotes: 2