viewForAnnotation not being called (using Alamofire to parse JSON) - swift

I'm new to iOS programming. I prefer to use Swift.
What I'm trying to do is call a web service that returns some JSON, parse the JSON into a custom object called Entry and plot those entries on a map.
I am using AlamoFire to make the web service call and SwiftyJSON to parse the JSON into Entry objects like below
request(.GET, URLString: "https://myURLThatReturnsJson")
.responseJSON { (_, _, json, error) in
if let json: AnyObject = json{
let jsonEntries = JSON(json)
for result in jsonEntries["entries"].arrayValue {
let business_name = result["business_name"].stringValue
let latitude = result["lat"].stringValue
let longitude = result["lng"].stringValue
let category = result["category"].stringValue
let address = result["address"].stringValue
let city = result["city"].stringValue
let type = result["type"].stringValue
let location = result["location"].stringValue
let valid = result["valid"].stringValue
let comments = result["comments"].stringValue
let date = result["date"].stringValue
let username = result["username"].stringValue
let additional_comment_count = result["additional_comment_count"].stringValue
let entry : Entry = Entry(
business_name: business_name,
latitude: latitude,
longitude: longitude,
category: category,
address: address,
city: city,
type: type,
location: location,
valid: valid,
comments: comments,
date: date,
username: username,
additional_comment_count: additional_comment_count,
title: business_name,
subtitle: location,
coordinate: CLLocationCoordinate2D(latitude: (latitude as NSString).doubleValue, longitude: (longitude as NSString).doubleValue))
entries.append(entry)
}
// Now we have our array of entries. Now plot them.
for entry in entries{
self.mapView.addAnnotation(entry)
}
}
}
So as you can see in the code above I am also mapping the entries within the AlamoFire request (don't know if this is bad because AlamoFire is done on a separate thread I believe).
I also have a viewForAnnotations method that looks like below
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
print("viewForAnnotation called")
return nil
}
My viewForAnnotation method is not being called at all (break point on print line is not being hit).
Any help is appreciated!

Please make sure to set the delegate property of mapview to self.
mapview.delegate = self
You can also do this by connecting the delegate outlet of mapview to ViewController using Connection Inspector (Interface Builder)

Related

Swift error: Cannot assign to property: 'coordinate' is immutable

The problem is when I write this line of code on which I try to convert the user location to another coordinate, I get this error: Cannot assign to property: 'coordinate' is immutable
The code is:
var location = sender.location(in: self.mapView)
let locCoord = self.mapView.convert(location, toCoordinateFrom: self.mapView)
self.mapView.userLocation.coordinate = locCoord *// Cannot assign to property:'coordinate' is immutable*
Can you help please?
As the error says the coordibate property is immutable
var coordinate: CLLocationCoordinate2D { get }
var userLocation: CLLocation { get }
you can't alter it in addition to userLocation , if you need a different location go directly with
If your want to assign new value then use var instead of let , let is used to non changed values

Swift - GoogleMaps SDK get coordinates on touch

I am new to swift and the Google Maps SDK, and was wondering how to get the coordinates of where the user has tapped using the Google Maps SDK. For example if a user holds their finger down on a certain place on a map, a annotation is created there. I would really appreciate your help, thanks.
In the GMSMapViewDelegate there is a method named: mapView:didLongPressAtCoordinate: which is called after a long-press gesture at a particular coordinate. See the reference here.
By implementing this method you could then add a marker to the map view:
func mapView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D) {
let marker = GMSMarker(position: coordinate)
marker.title = "Hello World"
marker.map = mapView
}
For a tap gesture a similar delegate method can be implemented called mapView:didTapAtCoordinate: which can be used in a similar way:
func mapView(mapView: GMSMapView!, didTapAtCoordinate coordinate: CLLocationCoordinate2D) {
print("Tapped at coordinate: " + String(coordinate.latitude) + " "
+ String(coordinate.longitude))
}
Try this
extension ViewController: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D)
{
print("Tapped at coordinate: " + String(coordinate.latitude) + " "
+ String(coordinate.longitude))
}
}
For Swift 5.0+
First, make sure you have added GMSMapViewDelegate delegate to your ViewController Class
Add this default function in your class
func mapView(_ mapView: GMSMapView, didLongPressAt coordinate: CLLocationCoordinate2D) {
debugPrint("Coordinates: ", coordinate)
}
If you just want coordinates, then above function is perfect for you But if you want to create Marker or get local address from touch then see below function
func mapView(_ mapView: GMSMapView, didLongPressAt coordinate: CLLocationCoordinate2D) {
let marker = GMSMarker(position: coordinate) //Add this line if you want to add marker
let decoder = CLGeocoder()
decoder.reverseGeocodeLocation(CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)) { placemarks, err in
if let placeMark = placemarks?.first {
let plName = placeMark.name ?? placeMark.subThoroughfare ?? placeMark.thoroughfare! //Place Name
var address : String! = "" //This will be the local address
if let subLocality = placeMark.subLocality ?? placeMark.name {
address.append(subLocality)
address.append(", ")
}
if let city = placeMark.locality ?? placeMark.subAdministrativeArea {
address.append(city)
address.append(", ")
}
if let state = placeMark.administrativeArea, let country = placeMark.country {
address.append(state)
address.append(", ")
address.append(country)
}
// Add Marker:
marker.title = plName
marker.snippet = address
marker.appearAnimation = .pop
marker.map = mapView
}
}
}
This Function does not only get coordinates but creates a Marker with all the details fetched from the coordinates(Like PlaceName, city, state,country etc.) too
If you just want local address, then remove all the code lines related to marker
The reason why i have used CLGeocoder and not GMSGeocoder from
GoogleMapDelegate is that Apple's CLGeocoder is much more precise
in getting the place-name while GMSGeocoder does not fetch
place-name accurately.
Also Note that : Apple's Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may
cause some of the requests to fail.

Use detailCalloutAccessoryView as third Annotation property

I am a newbie at Swift as well as Stackoverflow, and I was wondering if there is a way to import data from my plist to detailCalloutAccesoryView in the same manner as I import to "title" and "subtitle" in my map annotation callouts? This way, I could avoid having to make my own custom map annotation, and rather use the built-in features of MapKit.
I am calling my variables to title and subtitle like this:
var myData: NSArray?
if let path = NSBundle.mainBundle().pathForResource("myData", ofType: "plist") {
myData = NSArray(contentsOfFile: path)
}
if let items = myData {
for item in items {
let lat = item.valueForKey("Latitude") as! Double
let long = item.valueForKey("Longitude") as! Double
let myAnnotation = Mydata(value1: "Value1", value2: "Value2", value3: "Value3", latitude: lat, longitude: long)
// Define "Value1 to be shown as title in Callout
myAnnotation = item.valueForKey("Value1") as? String
// Define "Value2 to be shown as subtitle in Callout
myAnnotation.subtitle = item.valueForKey("Value2") as? String
annotations.append(myAnnotation)
}
}
return annotations
}
By now, I am just showing the same value in all annotations for detailCalloutAccessoryView in the place of subtitle using the following piece code:
let detailAccessory = UILabel(frame: CGRectMake(0,0,50,30))
detailAccessory.text = "Value3" // Obviously, shows constant value for all annotations
detailAccessory.font = UIFont(name: "Verdana", size: 10)
pinView?.detailCalloutAccessoryView = detailAccessory
Please, do not let my ignorance annoy you...
You will need to implement func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? delegate method. In there you can customize detailCalloutAccessoryView.
For detailed explanation refer to https://developer.apple.com/videos/play/wwdc2015/206/

Convert PFGeopoint lat and long from Parse to display current users distance in radius to other users onto a text label

The first image is an example of what the result is returning and the second image is an example of the results page, 'distance' is the label that I need to change in order to display my users distance. I have all my users locations stored on Parse as PFGeoPoint called "location" in lat and long. I then have a tabelViewCell with a textLabel. All users are shown on the VC and I am trying to show how far these users are from the current user like in Tinder.
I have the other users locations running in the logs as lat and long coordinates and I have the text label updating from "distance" to "[] km away!" So I must be getting the array back but its returning empty.
I have searched the internet and can't seem to figure it out. All the tutorials are all obj c or json or to add annotations in mapView. Here is my code on my usersResultsViewController:
var locationManager : CLLocationManager!
var latitude: CLLocationDegrees = 0
var longitude: CLLocationDegrees = 0
#IBAction func done(sender: UIBarButtonItem) {
self.performSegueWithIdentifier("backToProfile", sender: self)
}
#IBOutlet var resultsPageTableView: UITableView!
var imageFiles = [PFFile]()
var instrumentText = [String]()
var nameText = [String]()
var ageText = [String]()
var locationText = [PFGeoPoint]()
var usersLocations = Double
let roundedTwoDigitDistance = Double
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
// start of tableView:
let query = PFQuery(className: "_User")
query.whereKey("username", notEqualTo:PFUser.currentUser()!.username!)
query.findObjectsInBackgroundWithBlock { (users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// success
print(users!.count)
for user in users! {
self.imageFiles.append(user["image"] as! PFFile)
self.instrumentText.append(user["instrument"] as! String)
self.nameText.append(user["name"] as! String)
self.ageText.append(user["age"] as! String)
// self.locationText.append(user["location"] as! PFGeoPoint)
}
// reload the table forget this it will load nothing
self.resultsPageTableView.reloadData()
} else {
print("error")
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// *** note to self: for the return here it must be a mandatory field for user look at this again nd change it to mandatory age or username or something.
return imageFiles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let singleCell: CustomCell = tableView.dequeueReusableCellWithIdentifier("mySingleCellid") as! CustomCell
// text
singleCell.usersInstrument.text = instrumentText[indexPath.row]
singleCell.userName.text = nameText[indexPath.row]
singleCell.userAge.text = ageText[indexPath.row]
let query = PFUser.query()!
if let latitude = PFUser.currentUser()?["location"]?.latitude {
if let longitude = PFUser.currentUser()?["location"]?.longitude {
print(latitude)
print(longitude)
query.whereKey("username", notEqualTo:PFUser.currentUser()!.username!)
query.whereKey("location", withinGeoBoxFromSouthwest: PFGeoPoint(latitude: latitude - 10, longitude: longitude - 10), toNortheast:PFGeoPoint(latitude:latitude + 10, longitude: longitude + 10))
query.findObjectsInBackgroundWithBlock { (users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// success
for user in users! {
singleCell.userDistance.text = "\(self.locationText) km away!"
here are some of the forums i have found helpful but I am still stuck!!!:
http://www.scriptscoop.com/t/a2d00e357960/ios-converting-a-pfgeopoint-lat-and-long-from-parse-into-a-cllocation-lat-.html
trying to access the subscript of a Parse query array in SWIFT
Two Query Constraints On One Key with Parse and Swift
PFGeopoints have methods called "distanceInMilesTo:" and "distanceInKilometersTo:". These are what you're going to want to use. Call that method on the PFGeopoint storing the current user's location, and pass in each user's location from your query. Store the result in the appropriate label.
Here is the link to the API reference for this method: http://parse.com/docs/ios/api/Classes/PFGeoPoint.html#//api/name/distanceInKilometersTo:
Use the "near" feature and start getting all users within, say, 1 mile, then save that information, then get all users within 2 miles, then 5 miles, etc. Continuing to put geo-fences around a specific location and growing in concentric circles with increasing radius/distance, you can save the estimated distance for each user.
It's also possible to query for the set of objects that are contained within a particular area. To find the objects in a rectangular bounding box, add the withinGeoBox restriction to your Parse.Query.
var query = new Parse.Query(PlaceObject);
query.withinMiles("location", userGeoPoint, 10.0);
query.find().then(function(placesObjects) {
// Get a list of objects within 10 miles of a user's location
});
More examples are at https://www.parse.com/docs/js/guide

Using MapKit within the MVC guildines

All,
I am trying to follow MVC guidelines with my app, so I am removing the code from the View (View Controller). I am trying to get a MapKit displayed with lat/long from Parse. I have it working fine when I enter the lat/long manually.
So I want a computed property (I think) on the tuple. The tuple will hold the lat/long. When the tuple is used, I want to execute the parse query to retrieve the lat/long.
I am a little stuck putting a computed property on a tuple.
Here is my code.
var latAndlongTuple = (Double, Double)?
{
var query = PFQuery(className: "TableViewData")
query.includeKey("EventLoc")
query.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
var EventLocation = object["EventLoc"] as PFObject!
EventLocation.fetchIfNeededInBackgroundWithBlock {
(EventLocation: PFObject!, error: NSError!) -> Void in
dispatch_async(dispatch_get_main_queue()) {
let longitude = EventLocation["Latitude"] as NSString
let latitude = EventLocation["Longitude"] as NSString
}
func LocationCoordinate() -> MKCoordinateRegion
{
let location = CLLocationCoordinate2D(latitude: latAndlongTuple.0 ,longitude: latAndlongTuple.1)
let span = MKCoordinateSpanMake(0.001, 0.001)
let region = MKCoordinateRegion(center: location, span: span)
return region
}
So when the tuple is executed when the function LocationCoordinate is executed, I want the parse code running in the tuple computed property. Then it updates the segments of the tuple with the lat/long.
Any questions let me know.
I have done this by :
var latAndlongTuple:(lat : CLLocationDegrees, long : CLLocationDegrees)
{
get {
return (52.606907, -1.104780)
}
}
and instead of just returning the 2 values, I have used Parse to to get the values and then returned the CLLocationDegree tuple.