Right now I have a mapview that populates a bunch of pins based on a category, for example, 'Asian Food'. WHen i click the callout accessory of that annotation, the app navigates to a detail viewcontroller, which I hope to display the address and other properties. I am getting the information from the Yelp API CLient. Here is my model:
import UIKit
import MapKit
var resultQueryDictionary:NSDictionary!
class Resturant: NSObject {
var name: String!
var thumbUrl: String!
var address: String!
var jsonData: NSData!
var location: NSDictionary // location
init(dictionary: NSDictionary) {
name = dictionary["name"] as? String
thumbUrl = dictionary["thumbUrl"] as? String
address = dictionary["address"] as? String
self.location = dictionary["location"] as? NSDictionary ?? [:]
}
class func searchWithQuery(map: MKMapView, query: String, completion: ([Resturant]!, NSError!) -> Void) {
YelpClient.sharedInstance.searchWithTerm(query,sort: 0, radius: 1069, success: { (operation: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
let responseInfo = response as! NSDictionary
resultQueryDictionary = responseInfo
println(responseInfo)
let dataArray = responseInfo["businesses"] as! NSArray
for business in dataArray {
let obj = business as! NSDictionary
var yelpBusinessMock: YelpBusiness = YelpBusiness(dictionary: obj)
var annotation = MKPointAnnotation()
annotation.coordinate = yelpBusinessMock.location.coordinate
annotation.title = yelpBusinessMock.name
annotation.subtitle = yelpBusinessMock.displayAddress
attractionDetailAddressString = yelpBusinessMock.displayAddress
map.addAnnotation(annotation)
}
}) { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in
println(error)
}
}
// term: String, deal: Bool, radius: Int, sort: Int, categories: String, success: (AFHTTPRequestOperation!, AnyObject!) -> Void, failure: (AFHTTPRequestOperation!, NSError!) -> Void) -> AFHTTPRequestOperation! {
class func searchWithQueryWithRadius(map: MKMapView, term: String, deal: Bool, radius: Int, sort: Int, categories: String, completion: ([Resturant]!, NSError!) -> Void) {
YelpClient.sharedInstance.searchWithTerm(term, deal: false, radius: radius, sort: sort,categories: categories, success: { (operation: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
let responseInfo = response as! NSDictionary
resultQueryDictionary = responseInfo
println(responseInfo)
let dataArray = responseInfo["businesses"] as! NSArray
for business in dataArray {
let obj = business as! NSDictionary
var yelpBusinessMock: YelpBusiness = YelpBusiness(dictionary: obj)
var annotation = MKPointAnnotation()
annotation.coordinate = yelpBusinessMock.location.coordinate
annotation.title = yelpBusinessMock.name
map.addAnnotation(annotation)
}
}) { (operation: AFHTTPRequestOperation!, error: NSError!) -> Void in
println(error)
}
}
}
In my Attractions ViewController (where the user taps categories to populate the map with pins), I have this code for when the callout accessory is tapped:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
if (control == view.rightCalloutAccessoryView) {
let selectedLocation = view.annotation;
let selectedCoordinate = view.annotation.coordinate;
var latitude = selectedCoordinate.latitude
var longitude = selectedCoordinate.longitude
var location:CLLocation = CLLocation(latitude: latitude, longitude: longitude)
let businessPlacemark = MKPlacemark(coordinate: selectedCoordinate, addressDictionary: nil)
indicatedMapItem = selectedCoordinate;
let resturantMock:Resturant = Resturant(dictionary: resultQueryDictionary)
attractionDict = resturantMock.location;
performSegueWithIdentifier("attractionToDetail", sender: self);
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var attractionsDetailViewController:AttractionsDetailViewController = segue.destinationViewController as! AttractionsDetailViewController
attractionsDetailViewController.attractionLocation = indicatedMapItem;
attractionsDetailViewController.attractionLocationDetail = self.attractionDict
}
In my Detail View Controller, I attempt to get the data for the specific business that is tapped.
func getYelpData() {
var businessMock:Resturant = Resturant(dictionary: resultQueryDictionary)
var address:NSDictionary = attractionLocationDetail //dictionary
attractionDetailAddressString = businessMock.location["display_address"] as? String;
self.addressLabel.text = attractionDetailAddressString
}
However, this displays that address is nil.
Basically I want to know how to get one instance of the business from the large NSDictionary of Businesses and then retrieve the data for that individual business in my detail VC.
This is my Github repo if you want to look at all the code:
https://github.com/ssharif6/Park-N-Go
Thanks for any help!
It looks as though your problem may be here on this line
attractionDict = resturantMock.location;
Your dictionary will have a location key, but doesn't have the keys as suggested here on that location sub dictionary:
init(dictionary: NSDictionary) {
name = dictionary["name"] as? String
thumbUrl = dictionary["thumbUrl"] as? String
address = dictionary["address"] as? String
self.location = dictionary["location"] as? NSDictionary ?? [:]
}
So naturally all those values will be nil when your VC gets loaded.
Those keys however do exist on the resturantMock.business dictionary - is that what you meant to write? :)
Related
When the value of iImage is printed out it says: " size {3024, 4032} orientation 3 scale 1.000000", but then I get the error: "fatal error: unexpectedly found nil while unwrapping an Optional value" on the next line. How can the image I just got be nil?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let iImage = info[UIImagePickerControllerOriginalImage] as! UIImage
print(iImage)
createEvent(iImage)//where it is saved to cloudkit with location and title
EventPageViewController().eventPic.image = iImage
self.dismiss(animated: true, completion: nil);
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let eventPageViewController:EventPageViewController = storyboard?.instantiateViewController(withIdentifier: "EventPage") as! EventPageViewController
self.present(eventPageViewController, animated: true, completion: nil)
}
func loadEvent(_ completion: #escaping (_ error:NSError?, _ records:[CKRecord]?) -> Void)
{
let date = NSDate(timeInterval: -60.0 * 180, since: NSDate() as Date)
let predicate = NSPredicate(format: "creationDate > %#", date)
let query = CKQuery(recordType: "Event", predicate: predicate)
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil){
(records, error) in
if error != nil {
print("error fetching events: \(error)")
completion(error as NSError?, nil)
} else {
print("found events")
completion(nil, records)
guard let records = records else {
return
}
for record in records
{
self.drawEvents(record["LocationF"] as! CLLocation, title1: record["StringF"] as! String)
if let asset = record["Picture"] as? CKAsset,
let data = NSData(contentsOf: asset.fileURL),
let image1 = UIImage(data: data as Data)
{
//EventPageViewController().eventPic.image = image1
}
}
}
}
}
func drawEvents(_ loc: CLLocation, title1: String)
{
mapView.delegate = self
let center = CLLocationCoordinate2D(latitude: loc.coordinate.latitude, longitude: loc.coordinate.longitude)
let lat: CLLocationDegrees = center.latitude
let long: CLLocationDegrees = center.longitude
self.pointAnnotation1 = MKPointAnnotation()
self.pointAnnotation1.title = title1
self.pointAnnotation1.subtitle = "Event"
self.pointAnnotation1.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
self.pinAnnotationView = MKPinAnnotationView(annotation: self.pointAnnotation1, reuseIdentifier: nil)
self.mapView.addAnnotation(self.pinAnnotationView.annotation!)
}
This is because you are creating a new instance of your ViewController here EventPageViewController(). If this delegate method is in EventPageViewController, you can just call self.eventPic.image = iImage.
It looks like eventPic is a UIImageView as IBOutlet. So your instance of EventPageViewController hasn't finished loading its view and connecting outlets.
You should have a UIImage property in EventPageViewController.
class EventPageViewController {
var eventImage: UIImage?
#IBOutlet var eventPic:UIImageView! //you should have better name like eventImageView
func viewDidLoad() {
super.viewDidLoad()
eventPic?.image = eventImage
///rest goes here
}
}
so from you picker delegate you can access like:
let eventPageViewController = EventPageViewController()
eventPageViewController.eventImage = iImage
Hope this helps.
I am attempting to retrieve a PFFile image based off an objectID attached to the same object I am pulling the imageFile from. All other details are being pulled correctly (Strings) however, when I pull the PFFile, a random image from that class loads, not usually the matching image for that object, below is my code for how I am querying it and I am unsure where my logic is incorrect.
#objc(mapView:didTapMarker:) func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
if z == 0 {
print("\(marker) tapped")
let lat = marker.position.latitude as Double
let long = marker.position.longitude as Double
let tempMarker = getMarkerForLongAndLat(long: long, lat: lat, markers: markers)
let index = getIndexForMarker(marker: tempMarker)
getInfo(self.markersID[index]) {
PFFile in
self.imageFile?.getDataInBackground(block: { (imageData, error) in
if(imageData != nil) {
self.fullImage = UIImage(data: imageData!)
}
})
self.performSegue(withIdentifier: "playerSegue", sender: nil)
}
}
return true
}
func getInfo(_ markerID : String, completion: #escaping (PFFile) -> Void)
{
self.group.enter()
print("printing")
print(markerLat)
print(markerLong)
print(postsLat)
print(postsLong)
print("A")
let query = PFQuery(className:"Posted")
print("b")
print(markerID)
//query.whereKey("GPS", equalTo: PFGeoPoint(latitude: markerLat, longitude: markerLong))
query.whereKey("objectId", equalTo: "\(markerID)")
query.findObjectsInBackground { (objects, error) in
print("c")
if(error == nil)
{
print("d")
for object in objects!
{
print("made it")
self.imageFile = (object.object(forKey: "ImageFile") as! PFFile?)
self.userofVideo = object.object(forKey: "User") as AnyObject?
self.hour = object.object(forKey: "Hour") as AnyObject?
self.minutes = object.object(forKey: "minutes") as AnyObject?
self.streetName = object.object(forKey: "Location") as AnyObject?
self.tempLat = (object.object(forKey: "GPS")! as AnyObject).latitude as Double
self.tempLong = (object.object(forKey: "GPS")! as AnyObject).longitude as Double
self.currentText = (object.object(forKey: "text")! as Any? as! String?)
self.tempCoordinates = CLLocationCoordinate2D(latitude: self.tempLat! as CLLocationDegrees, longitude: self.tempLong! as CLLocationDegrees)
//print(tempLong)
}
}
else
{
print("didn't make it")
print(error)
}
completion(self.imageFile!)
}
self.group.leave()
}
Add the getDataInBackground inside the for loop. Here is a quick swift 3 example for your code.
#IBOutlet weak var fullImage : UIImageView!
let imageFile = (object.object(forKey: "ImageFile") as? PFFile)
imageFile?.getDataInBackground (block: { (data, error) -> Void in
if error == nil {
if let imageData = data {
//Here you can cast it as a UIImage or do what you need to
self.fullImage.image = UIImage(data:imageData)
}
}
})
I am having trouble with placing marker of bus station which was near me on my google map application.
Let say I live in New York City,I want all bus station with marker on my google map.But,i really don't know how to fetch all the bus station data of my current city(New York) and place a picker on it.I tried AppCoda tutorial and it gives me only marker of my custom search
Here : http://www.appcoda.com/google-maps-api-tutorial/
Is there any way that i can modified its "MapTask.Swift" code to get all bus station near me and place picker on each bus station?
Here is MapTask.Swift
import UIKit
import CoreLocation
import SwiftyJSON
class MapTask: NSObject {
let baseURLGeocode = "https://maps.googleapis.com/maps/api/geocode/json?"
var lookupAddressResults: Dictionary<NSObject, AnyObject>!
var fetchedFormattedAddress: String!
var fetchedAddressLongitude: Double!
var fetchedAddressLatitude: Double!
override init() {
super.init()
}
func geocodeAddress(address: String!, withCompletionHandler completionHandler: ((status: String, success: Bool) -> Void)) {
if let lookupAddress = address {
var geocodeURLString = baseURLGeocode + "address=" + lookupAddress
geocodeURLString = geocodeURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let geocodeURL = NSURL(string: geocodeURLString)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let geocodingResultsData = NSData(contentsOfURL: geocodeURL!)
let dictionary: Dictionary<NSObject, AnyObject> = try!NSJSONSerialization.JSONObjectWithData(geocodingResultsData!, options: NSJSONReadingOptions.MutableContainers) as! Dictionary<NSObject, AnyObject>
// Get the response status.
let status = dictionary["status"] as! String
if status == "OK" {
let allResults = dictionary["results"] as! Array<Dictionary<NSObject, AnyObject>>
self.lookupAddressResults = allResults[0]
print("The result is : \(dictionary)")
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"] as! String
let geometry = self.lookupAddressResults["geometry"] as! Dictionary<NSObject, AnyObject>
self.fetchedAddressLongitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lng"] as! NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as! Dictionary<NSObject, AnyObject>)["lat"] as! NSNumber).doubleValue
completionHandler(status: status, success: true)
}
else {
completionHandler(status: status, success: false)
}
})
}
else {
completionHandler(status: "No valid address.", success: false)
}
}
}
You'll need to call the Place Search with the type set to bus_station to limit the return to that specific type. The response will return results with their details that you can use to set the Markers.
I tried to get distance and duration from my position to selected marker on map using Google Directions API. I am using Alamofire + SwiftyJSON for doing the HTTP request and parsing the JSON that I get back.
I use this method for requesting and parsing JSON:
func fetchDistanceFrom(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D, completionHandler: (String?, String?, NSError?) -> ()) -> ()
{
let urlString = "https://maps.googleapis.com/maps/api/directions/json?key=\(apiKey)&origin=\(from.latitude),\(from.longitude)&destination=\(to.latitude),\(to.longitude)&mode=walking"
var parsedDistance : String?
var parsedDuration : String?
request(.GET, urlString)
.responseJSON { (req, res, json, error) in
if(error != nil) {
NSLog("Error: \(error)")
}
else {
var json = JSON(json!)
parsedDistance = json["routes"][0]["legs"][0]["distance"]["text"].stringValue as String!
parsedDuration = json["routes"][0]["legs"][0]["duration"]["text"].stringValue as String!
dispatch_async(dispatch_get_main_queue()) {
completionHandler(parsedDistance, parsedDuration, error)
}
}
}
}
And I use it in my MapView class like this:
func mapView(mapView: GMSMapView!, didTapMarker marker: GMSMarker!) -> Bool {
let selectedMarker = marker as! ExtendedMarker
dataProvider.fetchDistanceFrom(mapView.myLocation.coordinate, to: marker.position){ (parsedDistance, parsedDuration, error) in
let busStopDistance = parsedDistance
let busStopDuration = parsedDuration
selectedMarker.distance = "Distance: \(busStopDistance!)"
selectedMarker.duration = "Duration: \(busStopDuration!)"
}
return false
}
to show the info afterwards in this method:
func mapView(mapView: GMSMapView!, markerInfoContents marker: GMSMarker!) -> UIView! {
let selectedMarker = marker as! ExtendedMarker
if let infoView = UIView.viewFromNibName("MarkerInfoView") as? MarkerInfoView {
infoView.nameLabel.text = selectedMarker.name
infoView.linesTextView.text = selectedMarker.lines
infoView.distanceLabel.text = selectedMarker.distance
infoView.durationLabel.text = selectedMarker.duration
return infoView
} else {
return nil
}
}
But it doesn't work as it is supposed to, because somehow I can't get the distance and time on the first time I click the marker on map. It always shows up on second click on the marker. It looks like I get the JSON object from Google, it just isn't parsed before MapView shows the marker info.
Please, is there any way to fix this? I would be very thankful for any advice. Thank you.
I've got it working by forcing refresh on marker in didTapMarker method as shown below:
func mapView(mapView: GMSMapView!, didTapMarker marker: GMSMarker!) -> Bool {
let selectedMarker = marker as! ExtendedMarker
dataProvider.fetchDistanceFrom(mapView.myLocation.coordinate, to: marker.position){ (parsedDistance, parsedDuration, error) in
let busStopDistance = parsedDistance
let busStopDuration = parsedDuration
selectedMarker.distance = "Distance: \(busStopDistance!)"
selectedMarker.duration = "Duration: \(busStopDuration!)"
mapView.selectedMarker = nil;
mapView.selectedMarker = selectedMarker;
}
return false
}
for get distance google maps sdk ios with swift 2.0 and Alamomfire:
//get Distance two points
func getDistanceFrom(de: CLLocationCoordinate2D, para: CLLocationCoordinate2D){
let urlString = "https://maps.googleapis.com/maps/api/directions/json?key=\(GoogleMapsApiKey)&origin=\(de.latitude),\(de.longitude)&destination=\(para.latitude),\(para.longitude)&mode=walking"
Alamofire.request(.POST, urlString, parameters: nil, encoding: .JSON, headers: nil)
.responseJSON { (retorno) -> Void in
//print(retorno.result.value)
if retorno.result.isSuccess {
if let km = retorno.result.value?.objectForKey("routes")?[0].objectForKey("legs")?[0].objectForKey("distance") as? NSDictionary{
if let km_KM = km.valueForKey("text") as? String {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.LB_DistanciaKM.text = km_KM
})
}
}
}
}
}
With Swift 3 and Alamofire
func getGoogleMapGeoCodeDurationWithAlamofire(url: String) {
Alamofire.request(url).responseJSON { response in
let result = response.result
if let result = result.value as? Dictionary<String, AnyObject> {
if let routes = result["routes"] as? [Dictionary<String, AnyObject>] {
if let distance = routes[0]["legs"] as? [Dictionary<String, AnyObject>] {
print(distance[0]["distance"]?["text"] as! String)
print(distance[0]["duration"]?["text"] as! String)
}
}
}
}
}
Is there a way to get the Coordinates from an entered Address string using GMS in iOS Swift? I can find examples of returning an Address from Coordinates (reverse geocoding?), but not the other way around. Does google offer this service? To first parse the entered address string, return the most suitable actual address, and ultimately the coordinates. Please provide a simple example, or point me in the right direction. Regards, Chris
As of June 2015, GMS iOS SDK does not expose this functionality directly. However, there are two ways to get it. First one is using Google Maps Web API.
let baseURLGeocode = "https://maps.googleapis.com/maps/api/geocode/json?"
func geocodeAddress(address: String!, withCompletionHandler completionHandler: ((status: String, success: Bool) -> Void)) {
if let lookupAddress = address {
var geocodeURLString = baseURLGeocode + "address=" + lookupAddress
geocodeURLString = geocodeURLString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let geocodeURL = NSURL(string: geocodeURLString)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let geocodingResultsData = NSData(contentsOfURL: geocodeURL!)
var error: NSError?
let dictionary: Dictionary<NSObject, AnyObject> = NSJSONSerialization.JSONObjectWithData(geocodingResultsData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as Dictionary<NSObject, AnyObject>
if (error != nil) {
println(error)
completionHandler(status: "", success: false)
}
else {
// Get the response status.
let status = dictionary["status"] as String
if status == "OK" {
let allResults = dictionary["results"] as Array<Dictionary<NSObject, AnyObject>>
self.lookupAddressResults = allResults[0]
// Keep the most important values.
self.fetchedFormattedAddress = self.lookupAddressResults["formatted_address"] as String
let geometry = self.lookupAddressResults["geometry"] as Dictionary<NSObject, AnyObject>
self.fetchedAddressLongitude = ((geometry["location"] as Dictionary<NSObject, AnyObject>)["lng"] as NSNumber).doubleValue
self.fetchedAddressLatitude = ((geometry["location"] as Dictionary<NSObject, AnyObject>)["lat"] as NSNumber).doubleValue
completionHandler(status: status, success: true)
}
else {
completionHandler(status: status, success: false)
}
}
})
}
else {
completionHandler(status: "No valid address.", success: false)
}
}
You can find the complete description of this method here: http://www.appcoda.com/google-maps-api-tutorial/
Second way is to use apple native CoreLocation framework
func geoLocate(address:String!){
let gc:CLGeocoder = CLGeocoder()
gc.geocodeAddressString(address, completionHandler: { (placemarks: [AnyObject]!, error: NSError!) -> Void in
let pm = placemarks as! [CLPlacemark]
if (placemarks.count > 0){
let p = pm[0]
var myLatitude = p.location.coordinate.latitude
var myLongtitude = p.location.coordinate.longitude
//do your stuff
}
})
}
EDIT: Swift 4.2
func geoLocate(address:String!){
let gc:CLGeocoder = CLGeocoder()
gc.geocodeAddressString(address) { (placemarks, error) in
if ((placemarks?.count)! > 0){
let p = placemarks![0]
print("Lat: \(p.location?.coordinate.latitude) Lon: \(p.location?.coordinate.longitude)")
}
}
}