Google maps API Swift; Nil maneuver response - swift

I am trying to get the maneuver data for the given route from Google Maps' API. Running the code gives me nil values.
Here is the code I am running to get the maneuver data:
func getRouteSteps(source: CLLocationCoordinate2D,destination: CLLocationCoordinate2D) {
let session = URLSession.shared
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(source.latitude),\(source.longitude)&destination=\(destination.latitude),\(destination.longitude)&sensor=false&mode=driving&key=\(APIKey)")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error!.localizedDescription)
return
}
guard let jsonResult = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] else {
print("error in JSONSerialization")
return
}
guard let routes = jsonResult["routes"] as? [Any] else { return }
guard let route = routes[0] as? [String: Any] else { return }
guard let legs = route["legs"] as? [Any] else { return }
guard let leg = legs[0] as? [String: Any] else { return }
guard let steps = leg["steps"] as? [Any] else { return }
guard let duration = leg["duration"] as? [String: Any] else { return }
guard let distance = leg["distance"] as? [String: Any] else { return }
RouteData.append(RouteInfo(Time: String(describing: duration["text"]! as Any), Distance: String(describing: distance["text"]! as Any)))
for item in steps {
guard let step = item as? [String: Any] else { return }
guard let stepTurns = step["html_instructions"] as? String else { return }
guard let stepDistance = step["distance"] as? [String: Any] else { return }
guard let stepTime = step["duration"] as? [String: Any] else { return }
guard let polyline = step["polyline"] as? [String: Any] else { return }
guard let polyLineString = polyline["points"] as? String else { return }
guard let maneuver = step["maneuver"] as? Any else { return }
print(maneuver)
Here are the results:
nil
nil
Optional(turn-right)
Optional(turn-left)
Optional(ramp-left)
Optional(ramp-right)
Optional(straight)
Optional(turn-slight-left)
Optional(turn-slight-left)
Optional(turn-left)

Related

How to draw routes on GMSmap view swift (not straight) using google api Swift

I am trying to draw route on mapview using the google direction API . I am using the solution from the stackoverflow itself but i am getting some errors regarding intializers .
Also will the directions route will be same as google map itself or a straight one .
Any help is Appreciated. Also where should i call this methods
I am getting error as
Cannot invoke initializer for type 'GMSCoordinateBounds' with an argument list of type '(coordinate: String, String, coordinate:
String, String)'
Below is the Code .
func getRouteSteps(from source: CLLocationCoordinate2D, to destination: CLLocationCoordinate2D) {
let session = URLSession.shared
let url = URL(string: "https://maps.googleapis.com/maps/api/directions/json?origin=\(lat),\(long)&destination=\(directionlat),\(directionlong)&sensor=false&mode=driving&key=\(API KEY)")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error!.localizedDescription)
return
}
guard let jsonResult = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] else {
print("error in JSONSerialization")
return
}
guard let routes = jsonResult!["routes"] as? [Any] else {
return
}
guard let route = routes[0] as? [String: Any] else {
return
}
guard let legs = route["legs"] as? [Any] else {
return
}
guard let leg = legs[0] as? [String: Any] else {
return
}
guard let steps = leg["steps"] as? [Any] else {
return
}
for item in steps {
guard let step = item as? [String: Any] else {
return
}
guard let polyline = step["polyline"] as? [String: Any] else {
return
}
guard let polyLineString = polyline["points"] as? String else {
return
}
//Call this method to draw path on map
DispatchQueue.main.async {
self.drawPath(from: polyLineString)
}
}
})
task.resume()
}
Function to draw polyline
func drawPath(from polyStr: String){
let mapView: GMSMapView
let path = GMSPath(fromEncodedPath: polyStr)
let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 3.0
polyline.map = mapView // Google MapView
//
let cameraUpdate = GMSCameraUpdate.fit(GMSCoordinateBounds(coordinate: "\(lat)","\(long)", coordinate: "\(directionlat)","\(directionlong)")) as? [String : AnyObject]
mapView.moveCamera(cameraUpdate)
let currentZoom = mapView.camera.zoom
mapView.animate(toZoom: currentZoom - 1.4)
}
GMSCoordinatesBounds takes CLLocationCoordinates2D type as parameter, not String.
replace
let cameraUpdate = GMSCameraUpdate.fit(GMSCoordinateBounds(coordinate: "\(lat)","\(long)", coordinate: "\(directionlat)","\(directionlong)")) as? [String : AnyObject]
with
let cameraUpdate = GMSCameraUpdate.fit(GMSCoordinateBounds(coordinate: CLLocationCoordinate2D(latitude: Double(lat), longitude: Double(long)), coordinate: CLLocationCoordinate2D(latitude: Double(directionlat), longitude: Double(directionlat))))
and once you have added the mapView to your view controller and got the coordinates, call your function
self.getRouteSteps(from source: CLLocationCoordinate2D(latitude: Double(lat), longitude: Double(long)), destination: CLLocationCoordinate2D(latitude: Double(directionlat), longitude: Double(directionlat)))
You can try this, use the below to fetch direction:
//This function is used to fetch the directions from origin to destination
private func fetchDirection(destinationLat: CLLocationDegrees, destinationLong: CLLocationDegrees) {
//Here you need to set your origin and destination points and mode
if let location = locationManager.location {
guard let url = URL(string: "\("https://maps.googleapis.com/maps/api/directions/json")?origin=\(location.coordinate.latitude),\(location.coordinate.longitude)&destination=\(destinationLat),\(destinationLong)&key=\(Constants.MapKey)") else { return }
let task = URLSession.shared.dataTask(with: url) { [unowned self](data, response, error) -> Void in
do {
guard let data = data else { return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let mapData = try? decoder.decode(MapModel.self, from: data) else { return }
if let points = mapData.routes.first?.overviewPolyline.points {
self.drawRoute(points: points)
}
}
}
task.resume()
}
}
Use this to draw the route on the map:
//This function is used to draw the routes
private func drawRoute(points: String) {
let path = GMSPath.init(fromEncodedPath: points)
let singleLine = GMSPolyline.init(path: path)
self.polylines.append(singleLine)
singleLine.strokeWidth = 6.0
let gradientColor: GMSStrokeStyle = GMSStrokeStyle.gradient(from: .red, to: .blue)
singleLine.spans = [GMSStyleSpan.init(style: gradientColor)]
if self.polylines.count > 0 {
self.polylines.forEach{ $0.map = nil }
}
singleLine.map = self.mapView
}
And, here is the MapModel
struct MapModel: Decodable {
let status: String
let routes: [Routes]
}
struct Routes: Decodable {
let overviewPolyline: OverviewPolyline
}
struct OverviewPolyline: Decodable {
let points: String
}
I hope you are familiar with Codables and also, I have called the drawRoute function when I received the points.

Swift - Remove key and values from dictionary [String : Any]

I am trying to removed block users from a dictionary [String : Any] that I am grabbing from the database. At first I grab the list of UID's that the current user has blocked:
var updatedBlockList: Any?
func findBlockUsers() {
// find current users list of blocked users
guard let currentUserUid = Auth.auth().currentUser?.uid else { return }
let blockedUsers = Database.database().reference().child("users").child(currentUserUid)
blockedUsers.observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
var blockedList : Any
blockedList = userDictionary.keys
print(blockedList)
self.updateBlockList(blockedList: blockedList)
})
})
}
func updateBlockList(blockedList: Any) {
updatedBlockList = blockedList
print(updatedBlockList)
}
If I print updatedBlockList I get: ["gdqzOXPWaiaTn93YMJBEv51UUUn1", "RPwVj59w8pRFLf55VZ6LGX6G2ek2", "xmigo8CPzhNLlXN4oTHMpGo7f213"]
I now want to take those UID's (which will be the key in UserIdsDictionary and remove them after I pull ALL the users:
fileprivate func fetchAllUserIds() {
let ref = Database.database().reference().child("users")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
// attempting to remove the blocked users here without any luck
var updatedKey = key as String?
updatedKey?.remove(at: self.updatedBlockList as! String.Index)
print(updatedKey!)
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.fetchPostsWithUser(user: user)
})
}) { (err) in
print("Failed to fetch following user ids:", err)
}
}
I get this error when trying to remove: Could not cast value of type 'Swift.Dictionary.Keys' (0x1de2f6b78) to 'Swift.String.Index'
I'm sure i'm going about this the wrong way, but I know i'm close. The end goal is to take the blocked users UID's and remove them from the dictionary. Any help would be very much appreciated!!
Your forEach loop on userIdsDictionary is the wrong approach here so rather than trying to fix that code I would use a different approach and loop over the updatedBlockList
for item in updatedBlockList {
if let userID = item as? String {
userIdsDictionary.removeValue(forKey: userID)
}
}
For anyone wondering, here is the final changes that were made to make it work.
var updatedBlockList = [String]()
func findBlockUsers() {
// find current users list of blocked users
guard let currentUserUid = Auth.auth().currentUser?.uid else { return }
let blockedUsers = Database.database().reference().child("users").child(currentUserUid)
blockedUsers.observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
let blockedList = Array(userDictionary.keys)
print(blockedList)
self.updateBlockList(blockedList: blockedList)
})
})
}
func updateBlockList(blockedList: [String]) {
updatedBlockList = blockedList
print(updatedBlockList)
}
fileprivate func fetchAllUserIds() {
let ref = Database.database().reference().child("users")
ref.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
guard var userIdsDictionary = snapshot.value as? [String: Any], let self = self else { return }
for item in self.updatedBlockList {
userIdsDictionary.removeValue(forKey: item)
}
userIdsDictionary.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.fetchPostsWithUser(user: user)
})
}) { (err) in
print("Failed to fetch following user ids:", err)
}
}

How can i make an http get request with dictionary as parameter in swift

Here is the API Call how can I pass a dictionary as the parameter to the URL which is a get request, Don't want to use Alamofire in this.
The URL is - http://mapi.trycatchtech.com/v1/naamkaran/post_list_by_cat_and_gender_and_page?category_id=3&gender=1&page=1
func getDisplayJsonData(url: String, parameters: [String: Any], completion: #escaping displayCompletionHandler) {
guard let url = URL(string: url) else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
debugPrint(error?.localizedDescription ?? "")
completion(nil)
return
} else {
guard let data = data else {return completion(nil)}
let decoder = JSONDecoder()
do{
let displayJson = try decoder.decode(Display.self, from: data)
completion(displayJson)
} catch{
debugPrint(error.localizedDescription)
completion(nil)
return
}
}
} .resume()
}
}
Where I am calling this function here and passing the dictionary values.
extension DisplayVC {
func getData() {
let params = ["category_id": categoryId, "gender": genderValue, "page": pageNumber] as [String : Any]
DisplayService.instance.getDisplayJsonData(url: BASE_URL, parameters: params) { (receivedData) in
print(receivedData)
}
}
}
SWIFT 4 / Xcode 10
If this the only case, a simple solution is:
func getDisplayJsonData(url: String, parameters: [String: Any], completion: #escaping displayCompletionHandler) {
var urlBase = URLComponents(string: url)
guard let catValue = parameters["category_id"] as? Int else {return}
guard let genderValue = parameters["gender"] as? Int else {return}
guard let pageValue = parameters["page"] as? Int else {return}
urlBase?.queryItems = [URLQueryItem(name: "category_id", value: String(catValue)), URLQueryItem(name: "gender", value: String(genderValue)), URLQueryItem(name: "page", value: String(pageValue))]
//print(urlBase?.url)
guard let urlSafe = urlBase?.url else {return}
URLSession.shared.dataTask(with: urlSafe) { (data, response, error) in
//Your closure
}.resume()
}

Saving array of [String: Any] to user default crash occasionally?

I try to save an array of [String: Any] to user default, and for some situations it works, but others do not. I use the following to save to the default:
static func savingQueueToDisk(){
let queueDict = App.delegate?.queue?.map({ (track) -> [String: Any] in
return track.dict
})
if let queueDict = queueDict{
UserDefaults.standard.set(queueDict, forKey: App.UserDefaultKey.queue)
UserDefaults.standard.synchronize()
}
}
Queue is an array of Track, which is defined as follows:
class Track {
var dict: [String: Any]!
init(dict: [String: Any]) {
self.dict = dict
}
var album: Album?{
guard let albumDict = self.dict[AlbumKey] as? [String: Any] else{
return nil
}
return Album(dict: albumDict)
}
var artists: [Artist]?{
guard let artistsDict = self.dict[ArtistsKey] as? [[String: Any]] else{
return nil
}
let artists = artistsDict.map { (artistdict) -> Artist in
return Artist(dict: artistdict)
}
return artists
}
var id: String!{
return self.dict[IdKey] as! String
}
var name: String?{
return self.dict[NameKey] as? String
}
var uri: String?{
return self.dict[URIKey] as? String
}
}
I got different output when retrieving from the same API
Crashing output:
http://www.jsoneditoronline.org/?id=cb45af75a79aff64995e01e5efc0e7b6
Valid output:
http://www.jsoneditoronline.org/?id=0939823a4ac261bd4cb088663c092b20
It turns out it's not safe to just store an array of [String: Any] to the user defaults directly, and it might break based on the data it contains, and hence complaining about can't set none-property-list to user defaults. I solve this by first convert the array of [String: Any] to Data using JSONSerializationand now it can be saved correctly.
Solution:
//saving current queue in the app delegate to disk
static func savingQueueToDisk(){
if let queue = App.delegate?.queue{
let queueDict = queue.map({ (track) -> [String: Any] in
return track.dict
})
if let data = try? JSONSerialization.data(withJSONObject: queueDict, options: []){
UserDefaults.standard.set(data, forKey: App.UserDefaultKey.queue)
UserDefaults.standard.synchronize()
}else{
print("data invalid")
}
}
}
//retriving queue form disk
static func retrivingQueueFromDisk() -> [Track]?{
if let queueData = UserDefaults.standard.value(forKey: App.UserDefaultKey.queue) as? Data{
guard let jsonObject = try? JSONSerialization.jsonObject(with: queueData, options: []) else{
return nil
}
guard let queueDicts = jsonObject as? [[String: Any]] else{
return nil
}
let tracks = queueDicts.map({ (trackDict) -> Track in
return Track(dict: trackDict)
})
return tracks
}
return nil
}

Nested dataTaskWithRequest in Swift tvOS

I'm a C# developer convert to Swift tvOs and just starting to learn. I've made some progress, but not sure how to handle nested calls to json. The sources are from different providers so I can't just combine the query.
How do I wait for the inner request to complete so the TVSeries has the poster_path? Is there a better way to add the show to the collection and then process the poster path loading in another thread so it doesn't delay the UI Experience?
func downloadTVData() {
let url_BTV = NSURL(string: BTV_URL_BASE)!
let request_BTV = NSURLRequest(URL: url_BTV)
let session_BTV = NSURLSession.sharedSession()
//get series data
let task_BTR = session_BTV.dataTaskWithRequest(request_BTV) { (data_BTV, response_BTV, error_BTV) -> Void in
if error_BTV != nil {
print (error_BTV?.description)
} else {
do {
let dict_BTV = try NSJSONSerialization.JSONObjectWithData(data_BTV!, options: .AllowFragments) as? Dictionary<String, AnyObject>
if let results_BTV = dict_BTV!["results"] as? [Dictionary<String, AnyObject>]{
for obj_BTV in results_BTV {
let tvshow = TVSeries(tvDict: obj_BTV)
//for each tv series try to load a poster_path from secondary provider
if let str = obj_BTV["title"] as? String!{
let escapedString = str?.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())!
if let url = NSURL(string: self.SEARCH_URL_BASE + escapedString!) {
let request = NSURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print (error?.description)
} else {
do {
let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? Dictionary<String, AnyObject>
if let results = dict!["results"] as? [Dictionary<String, AnyObject>] {
//iterate through the poster array
for obj in results {
if let path = obj["poster_path"] as? String {
tvshow.posterPath = path
break
}
}
}
} catch let error as NSError {
print(error.description)
}
}
}
task.resume()
}
}
self.tvSeries.append(tvshow)
}
dispatch_async(dispatch_get_main_queue()){
self.collectionView.reloadData()
}
}
} catch let error as NSError {
print(error.description)
}
}
}
task_BTR.resume()
}
Thanks for your help!
I would recommend breaking things apart into multiple methods, with callbacks to sequence the operations, and utilizing Swift's built-in throws error handling mechanism. Here's an example, not perfect, but might help as a starting point:
class TVSeries
{
let title: String
var posterPath: String?
enum Error: ErrorType {
case MalformedJSON
}
init(tvDict: [String: AnyObject]) throws
{
guard let title = tvDict["title"] as? String else {
throw Error.MalformedJSON
}
self.title = title
}
static func loadAllSeries(completionHandler: [TVSeries]? -> Void)
{
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: BTV_URL_BASE)!) { data, response, error in
guard let data = data else {
print(error)
completionHandler(nil)
return
}
do {
completionHandler(try fromJSONData(data))
}
catch let error {
print(error)
}
}.resume()
}
static func fromJSONData(jsonData: NSData) throws -> [TVSeries]
{
guard let dict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments) as? [String: AnyObject] else {
throw Error.MalformedJSON
}
guard let results = dict["results"] as? [[String: AnyObject]] else {
throw Error.MalformedJSON
}
return try results.map {
return try TVSeries(tvDict: $0)
}
}
func loadPosterPath(completionHandler: () -> Void)
{
guard let searchPath = title.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet()) else {
completionHandler()
return
}
let url = NSURL(string: SEARCH_URL_BASE)!.URLByAppendingPathComponent(searchPath)
NSURLSession.sharedSession().dataTaskWithURL(url) { [weak self] data, response, error in
defer { completionHandler() }
guard let strongSelf = self else { return }
guard let data = data else {
print(error)
return
}
do {
strongSelf.posterPath = try TVSeries.posterPathFromJSONData(data)
}
catch let error {
print(error)
}
}.resume()
}
static func posterPathFromJSONData(jsonData: NSData) throws -> String?
{
guard let dict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments) as? [String: AnyObject] else {
throw Error.MalformedJSON
}
guard let results = dict["results"] as? [[String: AnyObject]] else {
throw Error.MalformedJSON
}
for result in results {
if let path = result["poster_path"] as? String {
return path
}
}
return nil
}
}
It might also be worth your time to look into something like RxSwift or Alamofire, which help you with these kinds of data-conversion / sequencing operations.