Can't get iOS MapKit Annotation to show title and subtitle - ios

I'm having issues showing a map annotation within xCode. I am able to show a pin on the correct coordinates, however I cannot seem to figure out how to show an annotation with a title and subtitle.
I'm a newbie to xCode and Swift, so apologies if I use incorrect terminology (or if this is a really simple problem to solve!) I've been searching the web for about 2 hours now, trying different variations to my code, but I can't get it working!
I'm developing a simple app where users can browse locations and then see it pinned on a map. Once people click through from a location to the MapViewController, I'm trying to just show a map with the location pinned and an annotation with title and subtitle. I've figured everything out except for the title and subtitle, so I'd appreciate any help!
override func viewDidLoad() {
super.viewDidLoad()
// Location pin
let initialLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
self.centerMapOnLocation(location: initialLocation)
// Annotation
let annotation = MKPointAnnotation()
annotation.title = self.location.name
annotation.subtitle = self.location.type
annotation.coordinate = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
mapView.addAnnotation(location)
}
// Map Center on Location
func centerMapOnLocation(location: CLLocation) {
let regionRadius: CLLocationDistance = 1000
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

I wonder if you should add annotation instead of location into the mapView, i.e.:
mapView.addAnnotation(annotation)
And if you want to show the callout programmatically, you can call like this:
mapView.selectAnnotation(annotation, animated: true)

Make sure your class conforms to the MGLMapViewDelegate and use the following method.
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}

Related

Custom MGLAnnotationView using Mapbox for IOS

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

how to delete one overlay when another is added in Xcode swift 3

I'm trying to build a function in my app which allows the user to drop a pin in a location and show a radius around that location. I only want one pin and one radius to show at a time.
I have worked out how to drop a pin and add the radius, and to remove the old pin when the new one is dropped, but cannot work out how to delete the circle overlay so that only one is show, around the most recent dropped pin.
Code is below for my map view controller. help much appreciated!
import UIKit
import CoreLocation
import MapKit
class MapVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
//connect map
#IBOutlet weak var mapInterface: MKMapView!
let manager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations[0] //all locations will be stored in CLLocation array, we request 0th element (the newest data which equals the most recent location)
print(location)
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.05, 0.05)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
print(location)
mapInterface.setRegion(region, animated: true)
self.mapInterface.showsUserLocation = true
}
//add pin drop
#IBAction func pinDrop(_ sender: UILongPressGestureRecognizer) {
//Pin drop annotation - get attributes of where to drop the pin
let location = sender.location(in: self.mapInterface)
let locCoord = self.mapInterface.convert(location, toCoordinateFrom: self.mapInterface)
let annotation = MKPointAnnotation()
//set pin characteristics
annotation.coordinate = locCoord
annotation.title = "Virtual location"
annotation.subtitle = "Dropped Pin"
//delete one pin once another is dropped
self.mapInterface.removeAnnotations(mapInterface.annotations)
//add pin annotation to map view
self.mapInterface.addAnnotation(annotation)
//print locCoord to console to check it worked
print(locCoord)
//create circle attributes
let cent = locCoord
let rad: Double = 500 //adjust radius to make circle bigger.
let circle = MKCircle(center: cent, radius: rad)
//print circle to console to check it worked
print(circle)
}
//add circle overlay
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay.isKind(of: MKCircle.self){
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.fillColor = UIColor.blue.withAlphaComponent(0.05)
circleRenderer.strokeColor = UIColor.blue
circleRenderer.lineWidth = 0.5
return circleRenderer
}
self.mapInterface.removeOverlays(overlay as! [MKOverlay])
return MKOverlayRenderer(overlay: overlay)
}
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
manager.stopUpdatingLocation()
}
}
Add the following code in pinDrop method.
self.mapInterface.removeOverlays(self.mapInterface.overlays)
mapView.removeOverlays(mapView.overlays) will do the trick
however, if i were you i'd rather update the annotation location instead of recreating a new one and remove the old one
to do that you will take that line
let annotation = MKPointAnnotation()
and put it above viewDidLoad method
and instead of :
//delete one pin once another is
self.mapInterface.removeAnnotations(mapInterface.annotations)
//add pin annotation to map view
self.mapInterface.addAnnotation(annotation)
it will be :
if self.mapInterface.annotations.count == 0 {
self.mapInterface.addAnnotation(annotation)
}

Swift - Using a UISwitch to change Pin Color

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.

How should I find the correct location data using userLocation in Mapbox for iOS SDK? (Swift)

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)
}

MapKit doesn't center on right location

I have a problem with my MapKit in Swift. I want it to center on a specific coordinate, but the default center doesn't change at all. The first (and most important part of) my viewController.swift file looks like this:
import UIKit
import Parse
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var map : MKMapView! = MKMapView()
var locManager:CLLocationManager!
#IBOutlet var txtUsername: UITextField!
#IBOutlet var txtPassword: UITextField!
override func viewDidLoad() {
var centerCoordinate = CLLocationCoordinate2DMake(41.8796677, -87.6198131)
var mapSpan = MKCoordinateSpanMake(0.01, 0.01)
var mapRegion = MKCoordinateRegionMake(centerCoordinate, mapSpan)
self.map.setRegion(mapRegion, animated: true)
locManager = CLLocationManager()
locManager.delegate = self
locManager.desiredAccuracy = kCLLocationAccuracyBest
locManager.requestAlwaysAuthorization()
map.showsUserLocation = true
super.viewDidLoad()
}
What could be the reason that the center of the map is not changed by the variable 'centerCoordinate'?
There are func's that are called automatically when the map is in use. One of them is
func mapViewDidFinishLoadingMap(mapView: MKMapView)
Try doing this.
func mapViewDidFinishLoadingMap(mapView: MKMapView) {
map.setRegion(mapRegion, animated: true)
}
If this doesn't work type func mapView and look in the list and find one you think might but I feel this is where it needs to be.
In ViewController.swift, find viewDidLoad and add the following to the end of the method:
// set initial location in Honolulu
let initialLocation = CLLocation(latitude: 21.282778, longitude: -157.829444)
You’ll use this to set the the starting coordinates of the map view to a point in Honolulu.
When you are trying to tell the map what to display, you can’t just give a latitude and longitude. That’s enough to center the map, but you need to specify the rectangular region to display to get a correct zoom level too.
Add the following constant and helper method to the class:
let regionRadius: CLLocationDistance = 1000
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
The location argument is the center point. The region will be have north-south and east-west spans based on a distance of regionRadius – you set this to 1000 meters (1 kilometer), which is a little more than half a mile. You then use regionRadius * 2.0 here, because that works well for plotting the public artwork data in the JSON file.
setRegion tells mapView to display the region. The map view automatically transitions the current view to the desired region with a neat zoom animation, with no extra code required!
Back in viewDidLoad, add the following line to the end of the method:
centerMapOnLocation(initialLocation)
This will call the helper method to zoom into initialLocation on startup.
Build and run the app, and now it should zoom in to the heart of Waikiki :]
Font: http://www.raywenderlich.com/90971/introduction-mapkit-swift-tutorial
Swift 5.
In the MKMapViewDelegate, I recommend using the regionDidChangeAnimated method and setting the region of the mapView based on the user's coordinate. Change the span according to your use case.
extension MapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
mapView.setRegion(MKCoordinateRegion(center: userLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)), animated: true)
}
}

Resources