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)
}
}
Related
The blue dot does not appear on the map. It appears there is some disconnect between the CLLocation manager and the self.mapView.userLocation.coordinate value.
The CLLocation manager correctly returns the Apple Campus coordinates, but the mapView.userLocation.coordinate value returns 0,0 for both latitude and longitude.
I have debugged this for hours and hours.
More Information Below:
In viewDidAppear and viewDidLoad, I printed the user's current location to the console as follows:
print(self.mapView.userLocation.coordinate)
This is the output that is rendered in the console:
CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
This is what my MapViewController looks like:
import UIKit
import CoreLocation
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var manager: CLLocationManager? = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// map stuff
manager?.delegate = self
manager?.desiredAccuracy = kCLLocationAccuracyBest
self.mapView.delegate = self
// print user's current location to console
print("user location in view did load:")
print(self.mapView.userLocation.coordinate)
}
override func viewDidAppear(_ animated: Bool) {
manager?.requestAlwaysAuthorization()
manager?.startUpdatingLocation()
self.mapView.showsUserLocation = true
self.mapView.userTrackingMode = .follow
// print user's current location to console
print("user location in view did appear:")
print(self.mapView.userLocation.coordinate)
animateMapToUserLocation()
}
}
Notes:
I have added the relevant Privacy messages to the Info.plist file.
The MapView in the storyboard is connected, with a solid circle, to the correct ViewController.
The MapViewController is instantiated as follows:
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let mapVC = storyboard.instantiateViewController(withIdentifier: "MapVC") as? MapViewController
self.present(mapVC!, animated: true, completion: nil)
Have you implemented the viewForAnnotation function? are you checking that you're not drawing (or failing to draw) a different sort of pin for the MKUserLocation?
e.g.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
...
}
Check also Settings > Privacy > Location Services > Your app
My control flow was off. The map would render before the app had permission to retrieve the user location. It is working now.
I don't believe this question has been asked yet in Swift 3.0 - three goals:
Upon viewDidLoad the Map Centers to the User Location at a certain zoom level that can be set (for example let span: MKCoordinateSpan = MKCoordinateSpanMake(40.0, 40.0))
Once the map loads and centers on the User Location, the User can then move and scroll the map to any other location WITHOUT the map automatically snapping back to the original User Location
Allow the User to ONLY zoom in to a certain level but allow the User to zoom out fully to view the entire global map (no restrictions on the zoom out level)
Here is my code thus far:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span: MKCoordinateSpan = MKCoordinateSpanMake(40.0, 40.0)
let userLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(userLocation, span)
mapView.setRegion(region, animated: true)
self.mapView.showsUserLocation = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
1. Should work with the code you have now.
2. Add check for subsequent location updates
In the didUpdateLocations method, add a Bool to check whether the region was centered on the user already or not.
var regionHasBeenCentered = false
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
if !regionHasBeenCentered {
let span: MKCoordinateSpan = MKCoordinateSpanMake(40.0, 40.0)
let userLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(userLocation, span)
mapView.setRegion(region, animated: true)
regionHasBeenCentered = true
}
self.mapView.showsUserLocation = true
}
Now the map will no longer center on the user after the first update, until you change regionHasBeenCentered back to false. This will allow the user to scroll and zoom freely.
3. Implement MKMapViewDelegate method to detect map region changes
Implement MKMapViewDelegate on your view controller so that you can check for region changes.
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
…and set the view controller as the delegate:
override func viewDidLoad() {
// other things…
mapView.delegate = self
}
Then implement the following method which will be called right before the region changes. Here you can check to see if the span's dimensions are too small, and set them to a minimum appropriate.
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
if mapView.region.span.latitudeDelta <= 40 && mapView.region.span.longitudeDelta <= 40 {
let minimumSpan = MKCoordinateSpan(latitudeDelta: 40, longitudeDelta: 40)
let minimumRegion= MKCoordinateRegion(center: mapView.centerCoordinate, span: minimumSpan)
mapView.setRegion(minimumRegion, animated: false)
}
}
Important note: From the MKCoordinateSpan documentation, the longitudeDelta will change as you move toward/away from the equator.
longitudeDelta
The amount of east-to-west distance (measured in degrees) to display for the map region. The number of kilometers spanned by a longitude range varies based on the current latitude. For example, one degree of longitude spans a distance of approximately 111 kilometers (69 miles) at the equator but shrinks to 0 kilometers at the poles.
Furthermore, MKCoordinateSpan's dimensions are measured in degrees, and 40 degrees is quite a bit so you probably want to change these values, otherwise the user will not be able to zoom in much at all.
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
}
Below is my code that lists 1 annotation pin. Right now its red. How can I make it blue? Do I need to put in a extension file? The code also includes the current users location. Also I could I make the annotation a button?
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet var map: MKMapView!
let manager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span: MKCoordinateSpan = MKCoordinateSpanMake(1.0, 1.0)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
map.setRegion(region, animated: true)
print(location.altitude)
print(location.speed)
self.map.showsUserLocation = true
}
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
let location:CLLocationCoordinate2D = CLLocationCoordinate2DMake(37.5656, -122.443)
let annoation = MKPointAnnotation()
annoation.coordinate = location
annoation.title = "MYSHOP"
annoation.subtitle = "Come Visit"
map.addAnnotation(annoation)
}
}
I see this question has been around a while, but I remember that I had trouble creating custom pin colors so maybe someone can use this in the future. MKPinAnnotationColor has been deprecated so you need to create your own colors if you want pins that are not red, green or purple. You do that by creating an extension to MKPinAnnotationView and defining a new color. For example, if you want a blue pin:
extension MKPinAnnotationView {
class func bluePinColor() -> UIColor {
return UIColor.blue
}
}
then assign the color to your annotation:
MKPinAnnotationView.bluePinColor()
You need to set up some object to serve as the map's delegate, and then implement viewForAnnotation. You can use MKPinAnnotationView to return standard pin annotations, or create a custom view and return that if you need something special.
There should be plenty of tutorials around showing how to do this since it's dirt-simple map customization.
A quick Google search revealed this SO post:
How to change MKAnnotation Color using Swift?
Note that the answer to that question uses pinColor, which was deprecated in iOS 10. You'll need to use MKPinAnnotationColor instead.
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)
}