Mapbox iOS SDK - visibleFeaturesAtPoint returns empty array - ios

I am trying the MGLMapView.visibleFeaturesAtPoint but am always getting back an empty array. Can someone tell me what I am doing wrong here?
Posted below is my code which is basically the adding the marker example (https://www.mapbox.com/ios-sdk/examples/marker/) but using the same point to get visible features at the marker point.
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
// Set the map’s center coordinate and zoom level.
mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: 40.7326808, longitude: -73.9843407), zoomLevel: 12, animated: false)
view.addSubview(mapView)
// Set the delegate property of our map view to `self` after instantiating it.
mapView.delegate = self
// Declare the marker `hello` and set its coordinates, title, and subtitle.
let hello = MGLPointAnnotation()
hello.coordinate = CLLocationCoordinate2D(latitude: 40.7326808, longitude: -73.9843407)
hello.title = "Hello world!"
hello.subtitle = "Welcome to my marker"
// Add marker `hello` to the map.
mapView.addAnnotation(hello)
//let ptTest = CGPoint(x: 1, y: 1)
print(mapView.visibleCoordinateBounds)
let ptTest = mapView.convertCoordinate(hello.coordinate, toPointToView: mapView)
print(ptTest)
print(mapView.visibleFeatures(at: ptTest))
}
// Use the default marker. See also: our view annotation or custom marker examples.
func mapView(mapView: MGLMapView, viewForAnnotation annotation: MGLAnnotation) -> MGLAnnotationView? {
return nil
}
// Allow callout view to appear when an annotation is tapped.
func mapView(mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
}

Question was answered by mapbox team on Github.
"Does the issue reproduce if you move the call to visibleFeatures(at:) to viewDidAppearAnimated(:) or mapViewDidFinishLoadingMap(:)? By the time viewDidLoad() runs, the view controller has loaded, but the map view may not have had a chance to load the style or tiles completely yet."
Apparently putting it in viewDidLoad() meant that the map had not fully loaded yet and so the features array was returned empty. Moving it to mapViewDidFinishLoadingMap() fixed the issue.

Related

Creating buttons for a popup(Speech Bubble) in Mapbox using Swift

I posted another question with the same code, but this question is different.
I want to add buttons in the bottom right corner of my Speech bubble that displays
Hello World!
Welcome to my marker!
I want to know how to place the buttons there, but if you want to know what the buttons would do, one of them would keep track of how many upvotes the bubble got by other users, and the other would send a request to another user.
Also, I found this example that looks like it implements a different version of a speech bubble(popup) that may be better to use
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Set the map’s center coordinate and zoom level.
mapView.setCenter(CLLocationCoordinate2D(latitude: 40.7326808, longitude: -73.9843407), zoomLevel: 12, animated: false)
view.addSubview(mapView)
// Set the delegate property of our map view to `self` after instantiating it.
mapView.delegate = self
// Declare the marker `hello` and set its coordinates, title, and subtitle.
let hello = MGLPointAnnotation()
hello.coordinate = CLLocationCoordinate2D(latitude: 40.7326808, longitude: -73.9843407)
hello.title = "Hello world!"
hello.subtitle = "Welcome to my marker"
// Add marker `hello` to the map.
mapView.addAnnotation(hello)
}
// Use the default marker. See also: our view annotation or custom marker examples.
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
return nil
}
// Allow callout view to appear when an annotation is tapped.
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
}
Below is what I would like my expected output to look like approximately
If you want to use the built-in Mapbox callout you may want to consider implementing the -mapView:rightCalloutAccessoryViewForAnnotation:
delegate method that allows you to further customize an MGLCallout as illustrated in this example: https://www.mapbox.com/ios-sdk/maps/examples/default-callout/. That delegate method returns a UIView so you can customize the UIView however you'd like to contain the buttons you want.
You'll notice in the example that another delegate method, -mapView:annotation:calloutAccessoryControlTapped: is also implemented. This gets called when the right callout accessory view (returned by -mapView:rightCalloutAccessoryViewForAnnotation:) is selected, so you could adapt this by placing your logic in that delegate method when a user selects the right side of the callout view.

Creating a custom map annotation callout with an image, accessing data from an array

I have a parsed a set of data from a JSON file (see example below):
{
"locations": [
{
"id": "0001",
"name": "Helensburgh Tunnels",
"type": "Tunnels, Beach, Views",
"location": "Helensburgh, South Coast",
"image": "Helensburgh-Tunnels.jpg",
"activity": "Walking, photography, tunnelling",
"isVisited": false,
"latitude": -34.178985,
"longitude": 150.992867
}
]
}
I am able to correctly read all of this data into a TableView (everything works correctly), however, I would also like to display all of the locations within the JSON file as annotations on a MapView. So far, everything appears correctly except for a preview image on the left side of the callout box (all the annotation pins appear, when clicked they show a callout with a title and subtitle, but no image).
What do I need to resolve so that I can show this image? I am able to achieve it in other sections of my app, where only one location is shown, however, I cannot seem to figure out how to add images to all the annotation callouts within this view.
This is the code I am using to populate the annotations in my NearMeMapViewController:
import UIKit
import MapKit
import CoreLocation
class NearMeMapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var nearMeMap: MKMapView!
let locationManager = CLLocationManager()
var locations = [Location]()
var location:Location!
override func viewDidLoad() {
super.viewDidLoad()
// parse json
if let locationJson = readLocation(){
if let locationArray = locationJson["locations"] as? [[String:Any]]{
for location in locationArray{
locations.append(Location.init(locationInfo: location))
}
print(locations.count)
}
}
// end parse json
nearMeMap.delegate = self
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.nearMeMap.showsUserLocation = true
// Show annotation
for location in locations {
let annotation = MKPointAnnotation()
annotation.title = location.name
annotation.subtitle = location.type
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
nearMeMap.addAnnotation(annotation)
}
}
// Start test - Custom annotation callout with image
func nearMeMap (_ nearMeMap: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "MyPin"
if annotation.isKind(of: MKUserLocation.self) {
return nil
}
// Reuse the annotation if possible
var annotationView:MKPinAnnotationView? = nearMeMap.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
}
let leftIconView = UIImageView(frame: CGRect.init(x: 0, y: 0, width: 53, height: 53))
leftIconView.image = UIImage(named: location.image)
annotationView?.leftCalloutAccessoryView = leftIconView
return annotationView
}
// End test
I would really appreciate any help! I'm a student and I'm just learning, so sorry if I've used any incorrect terminology or there are noob mistakes.
Swift 3.0
On above code the MKMapViewDelegate should be
func mapView (_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{...}
and not
func nearMeMap (_ nearMeMap: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {...}
Add var currentIndex = 0 as global declaration to identify the current image index. else, you aware to use id from JSON response.
i.e., Global Scope becomes like below code,
let locationManager = CLLocationManager()
var locations = [Location]()
//test variable? implemented to resolve issue with images in custom callout
var location:Location!
var currentIndex = 0
override func viewDidLoad() {...}
Then, the leftIconView will becomes,
let leftIconView = UIImageView(frame: CGRect.init(x: 0, y: 0, width: 53, height: 53))
var images = [String]()
for location in locations{
images.append(location.image)
}
leftIconView.image = UIImage(named: images[currentIndex])
self.currentIndex = self.currentIndex+1 //0,1,2,3...N.
Output:-
when you want to set custom image for annotation view, prefer MKAnnotationView instead of an MKPinAnnotationView. You can set your custom image as annotation by setting it as Image attribute of MKAnnotationView . Check here for more info on MKAnnotationView .
As MKPinAnnotationView is a subclass of MKAnnotationView, it has an image property but it generally (usually when you don't expect it) ignores it and displays its built-in pin image instead.
So it's best to just use a plain MKAnnotationView.
See this answer for more details.You can use MKAnnotationView as explained here.
----- EDIT -----
hide your basic Callout to show your custom image.
annotationView?.canShowCallout = false
Refer this ans to customize CallOut images. Check this link for the detailed tutorial.
Hope it helps. Happy Coding!!

mapView:rendererForOverlay delegate method never called when addOverlay is called on MKMapView

I created a MapViewController class with a scene that has an MKMapView in the storyboard. The MKMapView has an outlet mapView to MapViewController, and the delegate is set to MapViewController in the storyboard. The MapViewController is a MKMapViewDelegate, and I implemented the optional delegate method mapView:rendererForOverlay, and some other ones.
In viewDidAppear, I call addMapOverlay, defined as:
func addMapOverlay() {
let overlayRect = MKMapRectMake(
44.97077, -93.2864435,
0.1, 0.1)
let coordinate = CLLocationCoordinate2DMake(44.97087, -93.2865435)
let overlay = MapOverlay(coordinate: coordinate, rect: overlayRect)
print("adding overlay with level...")
mapView.delegate = self;
mapView.addOverlay(overlay, level: .AboveRoads)
}
Even though the delegate is set in the storyboard, I tried to see if would help if I set it here as well. (No difference.)
I implement the delegate methods as such:
extension MapViewController: MKMapViewDelegate {
func mapViewWillStartRenderingMap(mapView: MKMapView) {
print("mapViewWillStartRenderingMap...")
}
func mapView(mapView: MKMapView, didAddOverlayViews overlayViews: [AnyObject]) {
print("mapView:didAddOverlayViews...")
}
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay)
-> MKOverlayRenderer {
print("mapView:rendererForOverlay")
if overlay is MapOverlay {
let image = UIImage(named: "main_map_overlay")
let overlayView = MapOverlayView(overlay: overlay, overlayImage: image!)
return overlayView
}
return MKPolylineRenderer()
}
}
I am supposed to see the UIImage in the mapView with the coordinates, but I don't. The problem is that mapView:rendererForOverlay never gets called. What am missing? My only output is:
adding overlay with level...
mapViewWillStartRenderingMap...
Well I figured out the answer to my problem. Turns out, CLLocationCoordinate2DMake(44.97087, -93.2865435) is not the correct way to make the coordinate in this case. It's wrong one way or another, and as a result, adding the overlay silently fails without any helpful information. I did the following and it's fine now:
let parkTopLeft = MKMapPointForCoordinate(CLLocationCoordinate2D(
latitude: 44.972189,
longitude: -93.287307))
let parkBottomRight = MKMapPointForCoordinate(CLLocationCoordinate2D(
latitude: 44.967810,
longitude: -93.28186))
let overlayRect = MKMapRectMake(
parkTopLeft.x,
parkTopLeft.y,
parkBottomRight.x - parkTopLeft.x,
parkBottomRight.y - parkTopLeft.y)
let coordinate = CLLocationCoordinate2DMake(
overlayRect.origin.x + overlayRect.size.width / 2,
overlayRect.origin.y + overlayRect.size.height / 2)
let overlay = MapOverlay(coordinate: coordinate, rect: overlayRect)
mapView.addOverlay(overlay, level: .AboveRoads)

need mapView not to updateLocation

I have got mapView, and its delegate method
func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!) {
mapView.userTrackingMode = MKUserTrackingMode.None
var eye = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude)
var cam = MKMapCamera(lookingAtCenterCoordinate: eye, fromEyeCoordinate: eye, eyeAltitude: 10000)
UIView.animateWithDuration(1, animations: { () -> Void in
mapView.camera = cam
})
}
and it works some random way. it executes when view loads, and than when I am scrolling map to another country or city and zoom, it can(sometimes, now always) return me to my current location. It is curious cause my current location didnt changes and the delegate shouldnt execute.
I need do somehow this thing DONT HAPPENED. So i want to make my map zoom to current location only when location changed, in other way I need to have free map for scrolling.
You have to set you controller as CLLocationManagerDelegate, then declare a variable like:
var locationManager:CLLocationManager = CLLocationManager()
then in your viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.stopUpdatingLocation()
It should prevent to call func mapView(mapView: MKMapView!, didUpdateUserLocation userLocation: MKUserLocation!).
Pay attention that you have to re-enbale it in case you need to update map center with your coordinate without manual scrolling to you actual position.

How to overlay an image on a IOS map using swift

I am trying to find out how to overlay an image on a IOS map using SWIFT. I have created the following code that overlays a green circle on a map using map kit. I want to replace the green circle with the rectangular image tOver.png 500,500 I am new to iOS development and to swift. So far I can not find a swift example or good resource.
//
// ViewController.swift
// mapoverlaytest
//
import UIKit
import MapKit
class ViewController: UIViewController,MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self;
let location = CLLocationCoordinate2D(
latitude: 51.50007773,
longitude: -0.1246402
)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
let annotation = MKPointAnnotation()
annotation.setCoordinate(location)
annotation.title = "Big Ben"
annotation.subtitle = "London"
var overlay = MKCircle (centerCoordinate: location, radius: 500)
mapView.addOverlay(overlay)
mapView.addAnnotation(annotation)
}
func mapView(
mapView: MKMapView!, rendererForOverlay
overlay: MKOverlay!) -> MKOverlayRenderer! {
if (overlay.isKindOfClass(MKCircle))
{
var circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.strokeColor = UIColor.greenColor()
circleRenderer.fillColor = UIColor(
red: 0,
green: 1.0,
blue: 0,
alpha: 0.5)
return circleRenderer
}
return nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
As Totem explained, it would be simpler to use an image annotation instead of an overlay if that works for your purposes. It may not work depending on what you want to use this image for, however. The main difference between map overlays and map annotations, is annotations stay the same size when you zoom the map (like a pin), and overlays change with the size of the map (like marking a building). If you want your image to zoom with the map, it gets a little more complicated.
You will want to create a new MKOverlayRenderer subclass to draw your image. You have to draw the image into the image context yourself by subclassing the drawMapRect(mapRect, zoomScale, inContext) function. After you make this subclass, you can just substitute in your custom subclass in place of the MKCircleRenderer, and you should be good to go.
There is a very good write up on Raywenderlich.com, which you should definitely check out. It should walk you through everything you need to know.
Instead of rendererForOverlay, you should implement
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView!
Inside there, build your MKAnnotationView and set its image property before returning it out. Check out https://developer.apple.com/LIBRARY/ios/documentation/MapKit/Reference/MKAnnotationView_Class/index.html for more info on the MKAnnotationView class.

Resources