MKLocalSearch (search.start)always goes in the error flow. (SWIFT) - ios

I've been trying to learn swift for the past few days and just started my own project! As part of the project I wanted to show nearby cocktail bars on a map, I was able to find some nice info online and have been able to show a map with my current location. I also found info on how to find nearby locations: https://developer.apple.com/documentation/mapkit/mklocalsearch/request
Unfortunately this last one never seems to work, it just goes out of the function and does not return any locations, could anyone help me further? Below is the code of my viewcontroller with the function getNearbyLandmarks which doesn't work as intended.
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet var mapView: MKMapView!
let manager = CLLocationManager()
private var landmarks: [Landmark] = [Landmark]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
manager.stopUpdatingLocation()
render(location)
}
}
func render(_ location: CLLocation){
let coordinate = CLLocationCoordinate2D(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude)
let span = MKCoordinateSpan(latitudeDelta: 0.01,
longitudeDelta: 0.01)
let region = MKCoordinateRegion(center: coordinate,
span: span)
mapView.delegate = self
mapView.setRegion(region,
animated: true)
let pin = MKPointAnnotation()
pin.coordinate = coordinate
pin.title = "Current location"
mapView.addAnnotation(pin)
self.getNearByLandmarks()
updateAnnotations(from: mapView)
}
func mapView(_ mapViewIcon: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapViewIcon.dequeueReusableAnnotationView(withIdentifier: "custom")
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation,
reuseIdentifier: "custom")
annotationView?.canShowCallout = true
}else{
annotationView?.annotation = annotation
}
annotationView?.image = UIImage(named: "User")
return annotationView
}
private func getNearByLandmarks(){
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "coffee"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start{(response, error) in
if let response = response {
let mapItems = response.mapItems
self.landmarks = mapItems.map{
Landmark(placemark: $0.placemark)
}
}
}
}
private func updateAnnotations(from mapView: MKMapView){
let annotations = self.landmarks.map(LandmarkAnnotation.init)
mapView.addAnnotations(annotations)
}
}

You call getNearByLandmarks, and then immediately try to update the annotations:
self.getNearByLandmarks()
updateAnnotations(from: mapView)
but getNearByLandmarks is asynchronous, and takes some time to complete. You need to update the annotations whenever private var landmarks changes, one way is to update it in all the places that set that, like where you say
self.landmarks = mapItems.map{
Landmark(placemark: $0.placemark)
}
note you don't call updateAnnotations(from: mapView) there.
Or you could add a didSet property observer to landmarks itself so that updateAnnotations is called whenever it changes.

Related

How can I show my current location in maps in Xcode?

I watched a few lessons on Youtube and now tried to get hands-on experience. I am pursuing a little project which involves a tabbed app where I tried on the first page to create a map with a button showing the current location. Pretty basic actually. But somehow it just doesn’t work and I don’t know what’s the issue. Someone from CodewithChris told me this “I would suggest breaking up your app into smaller components and making sure each one works before going on to the next. Try outputting your location first before plotting it on a map etc so you can localize bugs easier.” I just don’t understand what she means by smaller components. I really appreciate all the help I can get. Below is the code as good as possible. Thanks in advance for your help.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet var textFieldForAddress: UITextField!
#IBOutlet var getDirectionsButton: UIButton!
#IBOutlet var map: MKMapView!
var locationManger = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManger.delegate = self
locationManger.desiredAccuracy = kCLLocationAccuracyBest
locationManger.requestAlwaysAuthorization()
locationManger.requestWhenInUseAuthorization()
locationManger.startUpdatingLocation()
map.delegate = self
}
#IBAction func getDirectionsTapped(_ sender: Any) {
getAddress()
}
func getAddress() {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(textFieldForAddress.text!) { (placemarks, error) in
guard let placemarks = placemarks, let location = placemarks.first?.location
else {
print("No Location Found")
return
}
print(location)
self.mapThis(destinationCord: location.coordinate)
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
}
func mapThis(destinationCord : CLLocationCoordinate2D) {
let souceCordinate = (locationManger.location?.coordinate)!
let soucePlaceMark = MKPlacemark(coordinate: souceCordinate)
let destPlaceMark = MKPlacemark(coordinate: destinationCord)
let sourceItem = MKMapItem(placemark: soucePlaceMark)
let destItem = MKMapItem(placemark: destPlaceMark)
let destinationRequest = MKDirections.Request()
destinationRequest.source = sourceItem
destinationRequest.destination = destItem
destinationRequest.transportType = .automobile
destinationRequest.requestsAlternateRoutes = true
let directions = MKDirections(request: destinationRequest)
directions.calculate { (response, error) in
guard let response = response else {
if let error = error {
print("Something is wrong :(")
}
return
}
let route = response.routes[0]
self.map.addOverlay(route.polyline)
self.map.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let render = MKPolylineRenderer(overlay: overlay as! MKPolyline)
render.strokeColor = .blue
return render
}
}
If you want to show the user location and have the map track it, you need to set two properties on the map view - showsUserLocation and userTrackingMode. In order for the map to have access to the user location you must receive whenInUse authorisation from the user, using CLocationManager.
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet var textFieldForAddress: UITextField!
#IBOutlet var getDirectionsButton: UIButton!
#IBOutlet var map: MKMapView!
var locationManger = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManger.requestWhenInUseAuthorization()
map.showsUserLocation = true
map.userTrackingMode = .follow
}
If you are running on the simulator then you need to simulate a location using the Debug menu in the simulator.
Okay, this information is surprising hard to find (just getting your own location!) — even after watching tutorials I had a hard time. But basically what you're missing is mapView.showsUserLocation = true
Here's the full code, if you need it...
import UIKit
import CoreLocation
import MapKit
class RadiusMapLocationViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
let coordinate = CLLocationCoordinate2DMake(33.97823607957177, -118.43823725357653)
var locationManager : CLLocationManager = CLLocationManager()
// Authorize use of location
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
mapView.showsUserLocation = (status == .authorizedAlways)
}
// Entering region
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
showAlert(withTitle: "You've entered \(region.identifier)", message: "Happy hopping!")
}
// Exiting region
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
showAlert(withTitle: "You've exited \(region.identifier)", message: "")
}
// Creating region and notifying when exit / enter
func region(with geotification: Geotification) -> CLCircularRegion {
let region = CLCircularRegion(center: geotification.coordinate,
radius: geotification.radius,
identifier: geotification.identifier)
region.notifyOnEntry = (geotification.eventType == .onEntry)
region.notifyOnExit = !region.notifyOnEntry
return region
}
// Monitoring region, if not "error"
func startMonitoring(geotification: Geotification) {
if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
showAlert(withTitle:"Error", message: "Geofencing is not supported on this device!")
return
}
}
func stopMonitoring(geotification: Geotification) {
for region in locationManager.monitoredRegions {
guard let circularRegion = region as? CLCircularRegion,
circularRegion.identifier == geotification.identifier else { continue }
locationManager.stopMonitoring(for: circularRegion)
}
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.userTrackingMode = .follow
mapView.showsUserLocation = true
// Region of coordinate
mapView.region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 800, longitudinalMeters: 800)
mapView.region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
let title = "Marina Bar Hop"
let restaurantAnnotation = MKPointAnnotation()
restaurantAnnotation.coordinate = coordinate
restaurantAnnotation.title = title
mapView.addAnnotation(restaurantAnnotation)
let regionRadius = 300.0
let circle = MKCircle(center: coordinate, radius: regionRadius)
mapView.addOverlay(circle)
self.locationManager.requestAlwaysAuthorization()
self.locationManager.delegate = self as? CLLocationManagerDelegate
//Zoom to user location
if let userLocation = locationManager.location?.coordinate {
let viewRegion = MKCoordinateRegion(center: userLocation, latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(viewRegion, animated: false)
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.strokeColor = UIColor.red
circleRenderer.lineWidth = 1.0
return circleRenderer
}
}
I have a few additional features on here if you need them. And it sounds like maybe you want to add a pin on the user's current location? It's included in this code too. I hope this helps! :)

Swift MapKit - Annotations dont show in the MapView

I am trying to create a MapView using MapKit framework which looks for the current user location and displays restaurants nearby. However, when I am trying to show annotations then they don't show up.
I have tried printing (response.mapItems) to check if it works and the result is good because it prints information about some restaurants that were found in the console.
Therefore, I don't know why these are not annotated on the map.
Here is the code that I've made:
import UIKit
import MapKit
import CoreLocation
class RestaurantViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var map: MKMapView!
let manager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.025, 0.025)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
map.setRegion(region, animated: true)
self.map.showsUserLocation = true
}
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "restaurant"
request.region = map.region
let search = MKLocalSearch(request: request)
search.start { (response, error) in
guard let response = response else {
return
}
for item in response.mapItems {
print(response.mapItems) - Console shows some restaunrant outputs that were correctly fetched
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
DispatchQueue.main.async {
self.map.addAnnotation(annotation)
}
}
}
}
}
You annotation implementation is a little off, allow me to explain how it should be.
Adding annotations to your map relies on two things.
A class conforming to MKAnnotation.
A subclass of MKAnnotationView.
Your map has annotations added to it but it does not know how to show them, so it shows nothing. You can tell it how to show them by implementing viewForAnimation. Take a look at my example below:
class PinAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D) {
self.coordinate = coordinate
super.init()
}
}
class PinAnnotationView: MKAnnotationView {
#available(*, unavailable)
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(annotation: PinAnnotation) {
super.init(annotation: annotation, reuseIdentifier: nil)
self.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.image = UIImage(named: "mypinimage") // or whatever.
}
}
Here we have the definition for the two objects I spoke of above.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let imageAnnotation = annotation as? ImageAnnotation {
let view = ImageAnnotationView(annotation: imageAnnotation)
return view
}
return nil
}
Here we have the implementation of viewForAnnotation that I spoke of above.
Implement viewForAnnotation
func mapView(_ mapView: MKMapView,viewFor annotation: MKAnnotation) -> MKAnnotationView?
see my demo here customPinAnnotationButton

How to change MapKit's pin color in Swift 3/4?

So, I am creating an app with MapKit. So, I need some help of changing the maps pin color from red to any color possible. I tried every way, I can't just not find a solution. Can any one check my code, and help me apply it to my code below of changing the map's pin tintColor. Thanks in advance.
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locations.count > 0 {
let location = locations.last!
print("Accuracy: \(location.horizontalAccuracy)")
if location.horizontalAccuracy < 100 {
manager.stopUpdatingLocation()
let span = MKCoordinateSpan(latitudeDelta: 0.014, longitudeDelta: 0.014)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.region = region
let location = CLLocation(latitude: latitude, longitude: longitude)
let place = Place(location: location, reference: reference, name: name, address: address)
self.places.append(place)
let annotation = MyHome(location: place.location!.coordinate, title: place.placeName)
DispatchQueue.main.async {
self.mapView.addAnnotation(annotation)
}
}
}
}
}
}
}
}
}
MyHome CLass
import Foundation
import MapKit
class MyHome: NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let title: String?
init(location: CLLocationCoordinate2D, title: String) {
self.coordinate = location
self.title = title
super.init()
}
}
MapView
fileprivate var locationManager = CLLocationManager()
fileprivate var heading: Double = 0
fileprivate var interactionInProgress = false
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
{
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required public init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
open override func viewDidLoad()
{
super.viewDidLoad()
self.mapView.isRotateEnabled = false
if let annotations = self.annotations
{
addAnnotationsOnMap(annotations)
}
locationManager.delegate = self
}
open override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
locationManager.startUpdatingHeading()
}
open override func viewDidDisappear(_ animated: Bool)
{
super.viewDidDisappear(animated)
locationManager.stopUpdatingHeading()
}
open func addAnnotations(_ annotations: [ARAnnotation])
{
self.annotations = annotations
if self.isViewLoaded
{
addAnnotationsOnMap(annotations)
}
}
fileprivate func addAnnotationsOnMap(_ annotations: [ARAnnotation])
{
var mapAnnotations: [MKPointAnnotation] = []
for annotation in annotations
{
if let coordinate = annotation.location?.coordinate
{
let mapAnnotation = MKPointAnnotation()
mapAnnotation.coordinate = coordinate
let text = String(format: "%#, AZ: %.0f, VL: %i, %.0fm", annotation.title != nil ? annotation.title! : "", annotation.azimuth, annotation.verticalLevel, annotation.distanceFromUser)
mapAnnotation.title = text
mapAnnotations.append(mapAnnotation)
}
}
mapView.addAnnotations(mapAnnotations)
mapView.showAnnotations(mapAnnotations, animated: false)
}
open func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading)
{
heading = newHeading.trueHeading
if(!self.interactionInProgress && CLLocationCoordinate2DIsValid(mapView.centerCoordinate))
{
let camera = mapView.camera.copy() as! MKMapCamera
camera.heading = CLLocationDirection(heading);
self.mapView.setCamera(camera, animated: false)
}
}
open func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool)
{
self.interactionInProgress = true
}
open func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool)
{
self.interactionInProgress = false
}
}
you have to change its color in MKMapViewDelegate delegate method
#IBOutlet weak var customMap: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
let anoot = MKPointAnnotation()
anoot.coordinate = CLLocationCoordinate2D.init(latitude: lat, longitude: lng)
customMap.addAnnotation(anoot)
customMap.delegate = self
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView.pinTintColor = UIColor.green
return annotationView
}
you can do it in your code like this
class ViewController: UIViewController {
var locationManager = CLLocationManager()
#IBOutlet weak var yourMap: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
yourMap.delegate = self
let anoot = MKPointAnnotation()
anoot.coordinate = CLLocationCoordinate2D.init(latitude: 23.0225, longitude: 72.5714)
yourMap.addAnnotation(anoot)
yourMap.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
}
extension ViewController: CLLocationManagerDelegate, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView.pinTintColor = UIColor.green
return annotationView
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locations.count > 0 {
let location = locations.last!
print("Accuracy: \(location.horizontalAccuracy)")
if location.horizontalAccuracy < 100 {
manager.stopUpdatingLocation()
let span = MKCoordinateSpan(latitudeDelta: 0.014, longitudeDelta: 0.014)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
yourMap.region = region
// let location = CLLocation(latitude: latitude, longitude: longitude)
// let place = Place(location: location, reference: reference, name: name, address: address)
// self.places.append(place)
//
// let annotation = MyHome(location: place.location!.coordinate, title: place.placeName)
//
// DispatchQueue.main.async {
//
// self.mapView.addAnnotation(annotation)
// }
}
}
}
}

MapKit zoom to user current location

I am trying to simply show user's location on the map, but I need to when app launches, the map should zoom to current location ,but I don't know why map doesn't zoom at all and it's like this :
Here is the code :
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
mapView.showsUserLocation = true
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
DispatchQueue.main.async {
self.locationManager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let location = locations.last as! CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
var region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
region.center = mapView.userLocation.coordinate
mapView.setRegion(region, animated: true)
}
I faced similar issue and wasted 4 days thinking whats going wrong. Finally resolved with creating these lines of code in viewDidLoad Method :
//Zoom to user location
let noLocation = CLLocationCoordinate2D()
let viewRegion = MKCoordinateRegionMakeWithDistance(noLocation, 200, 200)
mapView.setRegion(viewRegion, animated: false)
mapView.showsUserLocation = true
In ViewDidLoad Method add these new changes code :
override func viewDidLoad() {
super.viewDidLoad()
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
// Check for Location Services
if (CLLocationManager.locationServicesEnabled()) {
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}
//Zoom to user location
if let userLocation = locationManager.location?.coordinate {
let viewRegion = MKCoordinateRegionMakeWithDistance(userLocation, 200, 200)
mapView.setRegion(viewRegion, animated: false)
}
self.locationManager = locationManager
DispatchQueue.main.async {
self.locationManager.startUpdatingLocation()
}
}
Hope this helps to resolve your issue. Feel free to post comment if any further issue. Thanks
Here's another approach for Swift 3, XCode 8.2. First, write out a helper function:
let homeLocation = CLLocation(latitude: 37.6213, longitude: -122.3790)
let regionRadius: CLLocationDistance = 200
func centerMapOnLocation(location: CLLocation)
{
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
Then, call in in viewDidLoad()
mapView.showsUserLocation = true
centerMapOnLocation(location: homeLocation)
This will start the app with the location specified in the variable zoomed in.
In Swift 4.2 there has been changes with this code. Here is how it works now:
import UIKit
import MapKit
import CoreLocation
class MapVC: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
let authorizationStatus = CLLocationManager.authorizationStatus()
let regionRadius: Double = 1000
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
configureLocationServices()
}
func centerMapOnUserLocation() {
guard let coordinate = locationManager.location?.coordinate else {return}
let coordinateRegion = MKCoordinateRegion(center: coordinate, latitudinalMeters: regionRadius, longitudinalMeters: regionRadius)
mapView.setRegion(coordinateRegion, animated: true)
}
func configureLocationServices() {
if authorizationStatus == .notDetermined {
locationManager.requestAlwaysAuthorization()
} else {
return
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
centerMapOnUserLocation()
}
}
Code:
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapview: MKMapView!
let locationmanager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
mapview.mapType = MKMapType.standard
let location = CLLocationCoordinate2DMake(22.4651, 70.0771)
let span = MKCoordinateSpanMake(0.5, 0.5)
let region = MKCoordinateRegionMake(location, span)
mapview.setRegion(region, animated: true)
let annonation = MKPointAnnotation()
annonation.coordinate = location
annonation.title = "Chandi Bazar"
annonation.subtitle = "Jamnagar"
//
mapview.addAnnotation(annonation)
self.locationmanager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled()
{
locationmanager.delegate = self
locationmanager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationmanager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
locationmanager.stopUpdatingLocation()
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if (annotation is MKUserLocation)
{
return nil
}
let annotationidentifier = "Annotationidentifier"
var annotationview:MKAnnotationView
annotationview = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationidentifier)
let btn = UIButton(type: .detailDisclosure)
btn.addTarget(self, action: #selector(ViewController.hirenagravat(sender:)), for: .touchUpInside)
annotationview.rightCalloutAccessoryView = btn
annotationview.image = UIImage(named: "images (4).jpeg")
annotationview.canShowCallout = true
return annotationview
}
func hirenagravat(sender:UIButton)
{
let fvc = storyboard?.instantiateViewController(withIdentifier: "secondViewController") as? secondViewController
self.navigationController?.pushViewController(fvc!, animated: true)
}
In swift 4.1. To change the Zoom level you need to change the span value i.e MKCoordinateSpan(latitudeDelta: 0.95, longitudeDelta: 0.95)
let lat = "33.847105"
let long = "-118.2673272"
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!), span: MKCoordinateSpan(latitudeDelta: 0.95, longitudeDelta: 0.95))
DispatchQueue.main.async {
self.mapView.setRegion(region, animated: true)
}
Swift 5.0
let span = MKCoordinateSpan.init(latitudeDelta: 0.01, longitudeDelta:
0.01)
let coordinate = CLLocationCoordinate2D.init(latitude: 21.282778, longitude: -157.829444) // provide you lat and long
let region = MKCoordinateRegion.init(center: coordinate, span: span)
mapView.setRegion(region, animated: true)
when you set region -> you cannot zoom the map anymore. below to fix that
func yourFuncName() {
//this is global var
regionHasBeenCentered = false
if !self.regionHasBeenCentered {
let span: MKCoordinateSpan = MKCoordinateSpanMake(0.01, 0.01)
let userLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(_cllocationOfUserCurrentLocation!.coordinate.latitude, _cllocationOfUserCurrentLocation!.coordinate.longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(userLocation, span)
self.mapView.setRegion(region, animated: true)
self.regionHasBeenCentered = true
}
self.mapView.showsUserLocation = true
}
Try with MKMapViewDelegate func:
var isInitiallyZoomedToUserLocation: Bool = false
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
if !isInitiallyZoomedToUserLocation {
isInitiallyZoomedToUserLocation = true
mapView.showAnnotations([userLocation], animated: true)
}
}
func animateToUserLocation() {
if let annoation = mapView.annotations.filter ({ $0 is MKUserLocation }).first {
let coordinate = annoation.coordinate
let viewRegion = MKCoordinateRegion(center: coordinate, latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(viewRegion, animated: true)
}
}

Swift 3 - Understanding MKAnnotationView and mapView() function

There are some serious inconsistencies in answers due to Swift updating it's API spec constantly and not being graceful in it's deprecations... So I've decided to ask this question here.
My current code accounts for updating annotations through an asynchronous function that downloads an image off the internet as a remote URL. It can fetch the UIImage the data datatype no problem. However, I don't quite understand what is happening or the reasoning as to why we need to dequeue and engineer the solution this way.
The question is, why do we need to design our annotation this way, when we can just cast an annotation view to an annotation and not worry about every update?
Please review the following code and enlighten me:
import Foundation
import UIKit
import MapKit
import CoreLocation
class MapView: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
var firstCenter = false
var regionRadius: CLLocationDistance = 1000
var mapView: MKMapView!
var av:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
mapView = MKMapView(
frame:
CGRect(
x: 0,
y: 0,
width: view.frame.width,
height: view.frame.height
)
)
mapView.mapType = MKMapType.standard
mapView.isZoomEnabled = true
mapView.isScrollEnabled = true
mapView.delegate = self
view.addSubview(mapView)
mapView.center = view.center
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
print("Looking for locations")
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation = locations[0]
if firstCenter == false {
centerMapOnLocation(location: userLocation)
firstCenter = true
let location2d = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let url = URL(string: "https://graph.facebook.com/" + FB.id + "/picture?width=50&height=50")
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
let image = UIImage(data: data!)
let a = PinAnnotation()
a.title = "Netflix and Chill"
a.coordinate = location2d
a.image = image
self.mapView.addAnnotation(a)
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
}
}
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
}
public func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
internal func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "pin")
if annotationView != nil {
annotationView?.annotation = annotation
} else {
annotationView = MKAnnotationView(
annotation: annotation,
reuseIdentifier: "pin"
)
}
annotationView?.cornerRadius = 25
let anno = annotation as? PinAnnotation
annotationView?.image = anno?.image
return annotationView
}
}
class PinAnnotation: MKPointAnnotation {
var image:UIImage!
}
Thank you for your time, Chris
The reason they designed it this way was for performance reasons. This is a direct quote from the their official documentation.
"For performance reasons, you should generally reuse MKAnnotationView objects in your map views. As annotation views move offscreen, the map view moves them to an internally managed reuse queue. As new annotations move onscreen, and your code is prompted to provide a corresponding annotation view, you should always attempt to dequeue an existing view before creating a new one. Dequeueing saves time and memory during performance-critical operations such as scrolling."

Resources