I am very confused why this is displaying the default image instead of a round blue circle over New York. Any insight about this as well as when the default image is used will be greatly appreciated.
import UIKit
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setupMapview()
}
func setupMapview(){
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(CLLocationCoordinate2D(latitude: 40.74699, longitude: -73.98742), zoomLevel: 9, animated: false)
view.addSubview(mapView)
let annotation = MGLPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: 40.77014, longitude: -73.97480)
mapView.addAnnotation(annotation)
mapView.delegate = self
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
print("CORDINATE")
print(annotation.coordinate)
if annotation is MGLPointAnnotation {
print("SET\n\n\n")
let av = RoundedAnnotationView(annotation: annotation, reuseIdentifier: "ResuseIdentifier")
av.configure()
return av
}
return nil
}
}
class RoundedAnnotationView: MGLAnnotationView{
func configure(){
backgroundColor = .blue
layer.cornerRadius = 24
clipsToBounds = true
}
}
Output:
iPhone_Screen
print_statements
The standard default annotation is being shown in NY because that is exactly what you are adding to the map in setupMapview. If you want the map to display the user's location, you have to tell it to do so:
mapView.addAnnotation(annotation)
mapView.showsUserLocation = true // This needs to be set explicitly.
mapView.delegate = self
As usual, when you want to have access to the user's location you have to ask permission by inserting the correct flag in the info.plist:
Privacy - Location When In Use Usage Description
along with some kind of explanatory string:
"We'd like to track you with our satellite."
If you are running your app on the simulator you can create a custom location:
Simulator -> Features -> Location -> Custom Location...
Annotations should be added after the map has completely loaded. I have a more detailed step by step solution: https://github.com/mapbox/mapbox-gl-native/issues/16492
Related
I have an array of latitudes and another array of longitudes that I add to an array of type CLLocationCoordinate2D. I then use the new array to annotate multiple points on the map. Some, or most, or maybe even all of the annotations are displaying on the map but as I zoom in (yes, zoom IN), some of the annotations disappear, then come back, or dont. Any ideas on how to keep them all visible? This is behavior I would expect while zooming out, not in.
Here is the code i'm using for what i've described above.
import UIKit
import MapKit
import CoreLocation
class MultiMapVC: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var multiEventMap: MKMapView!
var latDouble = Double()
var longDouble = Double()
let manager = CLLocationManager()
var receivedArrayOfLats = [Double]()
var receivedArrayOfLongs = [Double]()
var locations = [CLLocationCoordinate2D]()
func locationManager(_ manager: CLLocationManager, didUpdateLocations uLocation: [CLLocation]) {
let userLocation = uLocation[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.3, 0.3)
let usersLocation = userLocation.coordinate
let region:MKCoordinateRegion = MKCoordinateRegionMake(usersLocation, span)
multiEventMap.setRegion(region, animated: true)
manager.distanceFilter = 1000
self.multiEventMap.showsUserLocation = true
}
func multiPoint() {
var coordinateArray: [CLLocationCoordinate2D] = []
print ("Received Longitude Count = \(receivedArrayOfLongs.count)")
print ("Received Latitude Count = \(receivedArrayOfLats.count)")
if receivedArrayOfLats.count == receivedArrayOfLongs.count {
for i in 0 ..< receivedArrayOfLats.count {
let eventLocation = CLLocationCoordinate2DMake(receivedArrayOfLats[i], receivedArrayOfLongs[i])
coordinateArray.append(eventLocation)
print (coordinateArray.count)
}
}
for events in coordinateArray {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: events.latitude, longitude: events.longitude)
multiEventMap.addAnnotation(annotation)
}
}
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
multiPoint()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
multiEventMap.removeFromSuperview()
self.multiEventMap = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
NiltiakSivad's solution works but it reverts to the old iOS 10 look. If you want to keep the new iOS 11 balloon markers for iOS 11 and use the old pin look only for older iOS versions then you can implement the delegate method as below:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "annotationView"
var view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if #available(iOS 11.0, *) {
if view == nil {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
view?.displayPriority = .required
} else {
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
}
view?.annotation = annotation
view?.canShowCallout = true
return view
}
The accepted answer from Leszek Szary is correct.
But there is some fineprint. Sometimes MKMarkerAnnotationViews are not rendered, even if
view.displayPriority = .required
is set.
What you are seeing is a combination of different rules.
MKAnnotationViews are rendered from top to bottom of the map. (It doesn't matter where north is).
If MapKit decides to draw overlapping MKAnnotationViews, then the MKAnnotationView nearer to the bottom is drawn on top (because it's drawn later)
Not only MKAnnotationViews, also titles rendered below MKMArkerAnnotationViews need space. The rendering of those titles is influenced by markerView.titleVisibility. If markerView.titleVisibility is set to .visible (instead of the default .adaptive), then this title is stronger than a MarkerAnnotationView that is rendered later, even if the later MarkerAnnotationView has a displayPriority = .required. The MarkerAnnotationView nearer to the bottom is not rendered.
This even happens if the MarkerAnnotationView nearer to the top has a low displayPriority. So a MarkerAnnotationView with low displayPriority and .titleVisibility = .visible can make a MarkerAnnotationView nearer to the bottom with displayPriority = .required disappear.
I am not aware of a documentation of this behaviour. This is the result of my experiments with iOS 12. My description is sipmplified.
I was experiencing a similar issue. My best guess is that it has something to do with how iOS 11 detects pin collisions. Implementing a custom annotation view or reverting to use the iOS 10 pin fixed the problem for me.
For example, implementing the following should fix your code:
class MultiMapVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? MKPointAnnotation else { return nil }
let identifier = "pin-marker"
var view: MKAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
}
return view
}
}
If this doesn't work, there is a displayPriority property that is worth looking into as it is responsible for helping to determine when pins should be hidden/shown at different zoom levels. More info at https://developer.apple.com/documentation/mapkit/mkannotationview/2867298-displaypriority
Hope this helps.
I was setting annotationView.displayPriority = .required only when the MKMarkAnnotationView was first allocated. Normally thats all you should need to do, but setting it each time the cell was reused fixed the issue for me.
I was seeing a similar problem with Xcode 10, and iOS 12+ as my deployment target. A post from an Apple staffer (https://forums.developer.apple.com/thread/92839) recommends toggling the .isHidden property on the dequeued marker. That has improved things but has not completely solved the problem.
result?.isHidden = true
result?.isHidden = false
return result
I have created function caricamappa() to loading map view with my mapbox map, and into Custompointannotation there is a control for add and remove annotation with Bool control, with a button i want to caricamappa() add and remove annotation with force Bool and its work but i don't want to re-add subview(mapview) into my app, is it possible to refresh a view without adding another and hide/show annotation? thanks
func carica_mappa() {
// Fill in the next line with your style URL from Mapbox Studio.
let styleURL = NSURL(string: "mapbox:***")
let mapView = MGLMapView(frame: view.bounds,
styleURL: styleURL as URL?)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Set the map’s center coordinate and zoom level.
mapView.setCenter(CLLocationCoordinate2D(latitude: 44.370417,
longitude: 7.411713),
zoomLevel: 13, animated: false)
view.addSubview(mapView)
mapView.userTrackingMode = .followWithHeading
// Set the delegate property of our map view to `self` after instantiating it.
mapView.delegate = self
let uno = CustomPointAnnotation(coordinate: CLLocationCoordinate2DMake(44.376362, 7.396907),
title: "**",
subtitle: "**",
controllo: visible)
// Set the custom `image` and `reuseIdentifier` properties, later used in the `mapView:imageForAnnotation:` delegate method.
uno.reuseIdentifier = "montagna"
uno.image = UIImage(named: "montagna")
if uno.controllo == true {
mapView.addAnnotation(uno)
}
else {
mapView.removeAnnotation(uno)
}
}
func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
if let point = annotation as? CustomPointAnnotation,
let image = point.image,
let reuseIdentifier = point.reuseIdentifier {
if let annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: reuseIdentifier) {
// The annotatation image has already been cached, just reuse it.
return annotationImage
} else {
// Create a new annotation image.
return MGLAnnotationImage(image: image, reuseIdentifier: reuseIdentifier)
}
}
// Fallback to the default marker image.
return nil
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
// Always allow callouts to popup when annotations are tapped.
return true
}
override func viewDidLoad() {
super.viewDidLoad()
carica_mappa()
}
My advice is to split the code to setup the map and the one to add the CustomAnnotationView. CaricaMappa() should only be used to add the map to its superview and called in viewDidLoad(). The code to setup the CustomAnnotation should be in its own method and you can call it on button tap. So you would have something like:
func carica_mappa() {
// Fill in the next line with your style URL from Mapbox Studio.
let styleURL = NSURL(string: "mapbox:***")
let mapView = MGLMapView(frame: view.bounds,
styleURL: styleURL as URL?)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Set the map’s center coordinate and zoom level.
mapView.setCenter(CLLocationCoordinate2D(latitude: 44.370417,
longitude: 7.411713),
zoomLevel: 13, animated: false)
view.addSubview(mapView)
mapView.userTrackingMode = .followWithHeading
// Set the delegate property of our map view to `self` after instantiating it.
mapView.delegate = self
}
#IBaction func didPressButton(sender: UIButton) {
let uno = CustomPointAnnotation(coordinate: CLLocationCoordinate2DMake(44.376362, 7.396907),
title: "**",
subtitle: "**",
controllo: visible)
// Set the custom `image` and `reuseIdentifier` properties, later used in the `mapView:imageForAnnotation:` delegate method.
uno.reuseIdentifier = "montagna"
uno.image = UIImage(named: "montagna")
if uno.controllo == true {
mapView.addAnnotation(uno)
}
else {
//You might want to check if the annotation exist in the map first.
mapView.removeAnnotation(uno)
}
}
I want to change the pin color from Red to Purple once the switch has been turned on. So far I have tried:
#IBAction func SwitchChanged(_ sender: Any){
if LegacySwitch.isOn == true {
annotation.pinTintColor = .purple
} else {
annotation.pinTintColor = .red
}
}
My switch is connected with:
#IBOutlet weak var LegacySwitch: UISwitch!
I created my pin in my ViewDidLoad. The coordinates of the pin come from another ViewController.
//Map Stuff
let Coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
annotation.coordinate = Coordinates
LocationMap.addAnnotation(annotation)
let center = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
self.LocationMap.setRegion(region, animated: true)
When ever I run the app, the pin continues to be red. The Action is encountered as I used a breakpoint to tell me it ran.
EDIT
I forgot to mention, I created the annotation variable above the ViewDidLoad.
var annotation = MyPointAnnotation()
I also have a MKPointAnnotation Class
class MyPointAnnotation: MKPointAnnotation {
var pinTintColor: UIColor?
}
Things that did not work:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
if LegacySwitch.isOn {
annotationView.pinTintColor = .purple
} else {
annotationView.pinTintColor = .red
}
return annotationView
}
Distinguish between:
An annotation: a lightweight bundle of characteristics
An annotation view: what you see, supplied on the basis of the annotation through a call to the map view delegate's mapView(_:viewFor:).
You are changing the former but not the latter. All the action in that regard happens in mapView(_:viewFor:), but you have not shown that — nor is there any particular reason why it would be called just because you change a property of an annotation sitting off in an instance variable somewhere. You need to replace the annotation in the map, so as to get the annotation view to be regenerated.
I'm looking create this map inside a UIView. Would this be possible? If so can you tell me how I can take this code to a UIView from UIViewController. I'm trying to put a MapBox map into a UIView and there are only instructions on how to import it into a UIViewController
How would this code look as a UIView class:
import Mapbox
class mapboxMap: UIViewController, MGLMapViewDelegate {
var mapView: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
// set the map's center coordinate
mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: 40.7326808,
longitude: -73.9843407),
zoomLevel: 10, 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"
mapView.addAnnotation(hello)
let hello2 = MGLPointAnnotation()
hello2.coordinate = CLLocationCoordinate2D(latitude: 40.7526808, longitude: -73.9843407)
hello2.title = "Hello world!"
hello2.subtitle = "Welcome to my marker"
mapView.addAnnotation(hello2)
}
// Use the default marker; see our custom marker example for more information
func mapView(mapView: MGLMapView, imageForAnnotation annotation: MGLAnnotation) -> MGLAnnotationImage? {
return nil
}
func mapView(mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
override func viewDidAppear(animated: Bool) {
// Wait a bit before setting a new camera.
// Create a camera that rotates around the same center point, back to 0°.
// `fromDistance:` is meters above mean sea level that an eye would have to be in order to see what the map view is showing.
let camera = MGLMapCamera(lookingAtCenterCoordinate: mapView.centerCoordinate, fromDistance: 9000, pitch: 45, heading: 0)
// Animate the camera movement over 5 seconds.
mapView.setCamera(camera, withDuration: 2.5, animationTimingFunction: CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut))
}
}
I think what you are trying to do is "add UIViewController in subview". Try to use Container view Controller. And following thread may helpful to you.
add UIViewController in subview
I'm trying to find the latitude and longitude of the user's location so that I can center the map on the user in viewdidload.
I've implemented what seems to be the right code but the values of userLat (latitude) and userLon (longitude) are way off.
N.B. Somebody else had the same problem as me but his answer was never resolved:
Mapbox iOS8 Swift mapView.showUsersLocation
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate, CLLocationManagerDelegate {
// let locationManager = CLLocationManager()
#IBOutlet weak var mapView: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Initalise map's center coordinate as vancouver
mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: 49.283382,
longitude: -123.117394),
zoomLevel: 15, animated: false)
view.addSubview(mapView)
// Set the delegate property of our map view to self after instantiating it.
mapView.delegate = self
// User location
mapView.showsUserLocation = true
let userLoc = mapView.userLocation!
userLoc.title = "Hello"
userLoc.subtitle = "I am here!"
let userLat = userLoc.coordinate.latitude
let userLon = userLoc.coordinate.longitude
print(userLat, userLon)
/*
self.locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}*/
}
func mapView(mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
}
Resulting print:
3.40282346638529e+38 3.40282346638529e+38
The strange thing is that the annotation works fine, and when I click my location I get the title and subtitle.
The easiest way to center the map on the user's location is to set MGLMapView.userTrackingMode = .follow (MGLUserTrackingModeFollow in Objective C). This will automatically move the map when a location is available.
The reason why you're seeing bogus numbers for MGLMapView.userLocation is that the user's location typically isn't available yet in viewDidLoad. Use the mapView:didUpdateUserLocation: delegate method to be notified when the user's location becomes available and when it updates.
There is a delegate method called mapViewDidFinishLoadingMap. Set the center of the map to the user coordinates in this method.
func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
mapView.setCenter((mapView.userLocation?.coordinate)!, animated: false)
}