In the image below, the 'My Location' annotation should be a blue circle. Instead, I get the balloon annotation. I'm pretty sure it has something to do with the last block of code but I don't know how to fix it. The surrounding annotations are fine - these a places I've added to the map.
I've removed the irrelevant bits of code:
class ExploreViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var exploreMapView: MKMapView!
let locationManger = CLLocationManager()
let regionInMeters: Double = 5000
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
getSchoolMarkers()
}
#IBAction func getCurrentLocation(_ sender: UIButton) {
centerViewOnUserLocation()
}
func setupLocationManager() {
locationManger.delegate = self
locationManger.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation() {
if let userLocation = locationManger.location?.coordinate {
let userRegion = MKCoordinateRegion.init(center: userLocation, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
exploreMapView.setRegion(userRegion, animated: true)
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
exploreMapView.showsUserLocation = true
centerViewOnUserLocation()
locationManger.startUpdatingLocation()
}
}
func getSchoolMarkers() {
// Code for creating annotations removed
self.exploreMapView.addAnnotation(schoolMarker)
}
}
extension ExploreViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let userLocation = locations.last else {return}
let currentLocation = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let userRegion = MKCoordinateRegion.init(center: currentLocation, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
exploreMapView.setRegion(userRegion, animated: true)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
}
extension ExploreViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var view = exploreMapView.dequeueReusableAnnotationView(withIdentifier: "reuseIdentifier") as? MKMarkerAnnotationView
if view == nil {
view = MKMarkerAnnotationView(annotation: nil, reuseIdentifier: "reuseIdentifier")
}
view?.annotation = annotation
view?.displayPriority = .required
return view
}
}
You need to return nil for the MKUserLocation in order to get the default annotation view:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation is MKUserLocation else {
return nil
}
var view = exploreMapView.dequeueReusableAnnotationView(withIdentifier: "reuseIdentifier") as? MKMarkerAnnotationView
if view == nil {
view = MKMarkerAnnotationView(annotation: nil, reuseIdentifier: "reuseIdentifier")
}
view?.annotation = annotation
view?.displayPriority = .required
return view
}
Related
I have a MapView in SwiftUi and I am trying to add a pin annotation to it when a user long presses a location on the map. I see this can easily be accomplished in swift however I am using SwiftUI. I do not know how to add the long-press detector. A code example would be great.
My MapView
struct MapView: UIViewRepresentable {
#Binding
var annotations: [PinAnnotation]
let addAnnotationListener: (PinAnnotation) -> Void
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.delegate = context.coordinator
view.addAnnotations(annotations)
if annotations.count == 1 {
let coords = annotations.first!.coordinate
let region = MKCoordinateRegion(center: coords, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
view.setRegion(region, animated: true)
}
}
func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(self)
}
}
MapViewCoordinator
class MapViewCoordinator: NSObject, MKMapViewDelegate {
var mapViewController: MapView
init(_ control: MapView) {
self.mapViewController = control
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let annotation = view.annotation
guard let placemark = annotation as? MKPointAnnotation else { return }
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
//Custom View for Annotation
let identifier = "Placemark"
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
annotationView.annotation = annotation
return annotationView
} else {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.canShowCallout = true
let button = UIButton(type: .infoDark)
annotationView.rightCalloutAccessoryView = button
return annotationView
}
}
}
The method to add a pin to a MapView
func addPinBasedOnGesture(gestureRecognizer:UIGestureRecognizer){
var touchPoint = gestureRecognizer.locationInView(mapView)
var newCoordinates = self.convertPoint(touchPoint, toCoordinateFromView: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinates
mapView.addAnnotation(annotation)
}
Below solution adds a pin at the point where user long presses on the Map.
Add below method in MapViewCoordinator
#objc func addPinBasedOnGesture(_ gestureRecognizer:UIGestureRecognizer) {
let touchPoint = gestureRecognizer.location(in: gestureRecognizer.view)
let newCoordinates = (gestureRecognizer.view as? MKMapView)?.convert(touchPoint, toCoordinateFrom: gestureRecognizer.view)
let annotation = PinAnnotation()
guard let _newCoordinates = newCoordinates else { return }
annotation.coordinate = _newCoordinates
mapViewController.annotations.append(annotation)
}
and longPress gesture code in func makeUIView(context: Context) -> MKMapView {}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
let longPressed = UILongPressGestureRecognizer(target: context.coordinator,
action: #selector(context.coordinator.addPinBasedOnGesture(_:)))
mapView.addGestureRecognizer(longPressed)
return mapView
}
Find below modified parts of provided code to get required behaviour:
struct SUMapView: UIViewRepresentable {
// ... other your code here
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
let longPressed = UILongPressGestureRecognizer(target:
context.coordinator, action: #selector(addPin(gesture:)))
mapView.addGestureRecognizer(longPressed)
return mapView
}
// ... other your code here
}
class MapViewCoordinator: NSObject, MKMapViewDelegate {
// ... other your code here
#objc func addPin(gesture: UILongPressGestureRecognizer) {
// do whatever needed here
}
// ... other your code here
}
So I am using Swift 4 and would like a pin to drop and then when the user touches the pin I would like it too come up with the text of that current location... When I run it the pin drops fine but after that I can not tap on the pin thus having no interaction at all with that marked point. I should note that I am using Mapbox.
import Foundation
import UIKit
import CoreLocation
import Mapbox
import MapKit
class SecondViewController: UIViewController, CLLocationManagerDelegate, MGLMapViewDelegate {
#IBOutlet var mapView: MGLMapView!
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func markStuff(_ sender: Any) {
}
#IBAction func refLocation(_ sender: Any) {
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
mapView.setCenter(center, zoomLevel: 10, animated: true)
let annotation = MGLPointAnnotation()
annotation.coordinate = location.coordinate
annotation.title = "\(annotation.coordinate)"
self.mapView.addAnnotation(annotation)
manager.stopUpdatingLocation()
}
}
Try this method to handle tap on pin
func mapView(mapView: MGLMapView, tapOnCalloutForAnnotation annotation: MGLAnnotation)
{
// Do something here
print("Do Something here You Want)
}
Also see mapbox documentation https://www.mapbox.com/ios-sdk/api/3.0.0/Protocols/MGLMapViewDelegate.html#//api/name/mapView:tapOnCalloutForAnnotation
If you would like to display the name of the location in a callout, you should have -mapView:annotationCanShowCallout: return true.
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
This allows the annotation to show a callout. You can also access the properties of that annotation (such as the title or subtitle) in that method. You may find this example helpful: https://www.mapbox.com/ios-sdk/examples/marker/
I am currently trying to create a map app that has around 20 pins. I have made a segue so that the Pin's Annotation's Title is transferred. However now I need to implement it so that a description is correctly selected from a series of 20 .txt file descriptions and 20 images. But I believe I am able to do it with one Segue.
Here is my current code
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate,CLLocationManagerDelegate {
#IBOutlet weak var MapView: MKMapView!
let manager = CLLocationManager()
var artworkPin:Artwork!
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//let location = locations[0]
//let span:MKCoordinateSpan = MKCoordinateSpanMake(0.02, 0.02)
//let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
}
override func viewDidLoad() {
super.viewDidLoad()
// tracking user's location
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
// Setting up Map
let distanceSpan:CLLocationDegrees = 2000
MapView.setRegion(MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(-39.0556253, 174.0752278), distanceSpan, distanceSpan), animated: true)
MapView.showsUserLocation = true
MapView.delegate = self
// artwork on map
let windwandcoord: CLLocationCoordinate2D = CLLocationCoordinate2DMake(-39.055961,174.072288)
artworkPin = Artwork(title:"Wind Wand",locationName:"Majestic",discipline:"Statue",
coordinate:windwandcoord)
MapView.addAnnotation(artworkPin)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MKUserLocation {return nil}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.calloutOffset = CGPoint(x: -5, y: 5)
let calloutButton = UIButton(type: .detailDisclosure)
pinView!.rightCalloutAccessoryView = calloutButton
pinView!.sizeToFit()
}
else {
pinView!.annotation = annotation
}
return pinView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "no", sender:self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let ViewTwo = segue.destination as! ViewTwo
ViewTwo.artworkPin = self.artworkPin
}
}
I also don't think this will give the correct title if I add another pin am I correct? Because it will simply take the name of artworkPin rather than of any variable pin added.
Thanks for any help.
I'm trying to set a custom callout view with a xib I created however it doesn't show up.
My xib LocationInfo looks like this
I've created a custom uiview class for the view in my xib to set a background image (not sure if this works since I haven't been able to show the xib)
import Foundation
import UIKit
import MapKit
class AddressView: MKPinAnnotationView{
override func draw(_ rect: CGRect) {
super.draw(rect);
UIGraphicsBeginImageContext(self.frame.size)
UIImage(named: "Location.Info-background")?.draw(in: self.bounds)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.backgroundColor = UIColor(patternImage: image!)
}
override func setSelected(_ selected: Bool, animated: Bool) {
//todo
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
//todo
return nil
}
}
My custom annotationpin class is as follows
import Foundation
import MapKit
import UIKit
class MapPin: MKPointAnnotation{
var name: String
var street: String
var type: String
var postCode: String
init(name: String, street: String, type: String, postCode: String){
self.name = name
self.street = street
self.type = type
self.postCode = postCode
}
}
and I'm trying to use this all as follows in my view controller class
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!) { (placemarks, error) in
if (error != nil){
return
}
if placemarks?.count != nil{
let pm = (placemarks?[0])! as CLPlacemark
self.displayLocationInfo(placemark: pm)
}
}
let spanX = 0.00725
let spanY = 0.00725
locationManager.stopUpdatingLocation()
let location = locations.last! as CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: spanX, longitudeDelta: spanY))
self.MapRSR.setRegion(region, animated: true)
self.MapRSR.delegate = self
let mapPin = MapPin(name: "", street: "", type: "", postCode: "")
mapPin.coordinate = center
mapPin.title = "test"
self.MapRSR.addAnnotation(mapPin)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let pin = mapView.dequeueReusableAnnotationView(withIdentifier: "LocationInfo") ?? AddressView(annotation: annotation, reuseIdentifier: "LocationInfo")
pin.canShowCallout = true
return pin
}
It just won't show my xib view. Does anyone know what I'm doing wrong or how I can achieve the effect I want which is something like this.
In didSelectAnnotationView load xib from bundle and add subview to the annotation view. here CustomXibCallout is xib file and CustomCalloutView is MKAnnotationView.
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
if view.annotation!.isKindOfClass(MKUserLocation){
return
}
//Custom xib
let customView = (NSBundle.mainBundle().loadNibNamed("CustomXibCallout", owner: self, options: nil))[0] as! CustomCalloutView;
let calloutViewFrame = customView.frame;
customView.frame = CGRect(x: -calloutViewFrame.size.width/2.23, y: -calloutViewFrame.size.height-7, width: 315, height: 200)
view.addSubview(customView)
}
in didDeselectAnnotationView remove the added view
func mapView(mapView: MKMapView, didDeselectAnnotationView view: MKAnnotationView)
{
for childView:AnyObject in view.subviews{
childView.removeFromSuperview();
}
}
Example for CustomCallout
In my app the user can search for a location and it adds a pin. I want a next button to go to another view controller and show the exact same map but in a smaller version. How can I move the exact same map to another view controller?
import UIKit
import MapKit
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController {
let locationManager = CLLocationManager()
var resultSearchController:UISearchController? = nil
var selectedPin:MKPlacemark? = nil
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
//
let locationSearchTable = storyboard!.instantiateViewControllerWithIdentifier("LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
//
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
navigationItem.titleView = resultSearchController?.searchBar
//
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
//
locationSearchTable.mapView = mapView
//
locationSearchTable.handleMapSearchDelegate = self
//
let button = UIButton(type: UIButtonType.System) as UIButton
button.frame = CGRectMake(100, 100, 100, 50)
button.backgroundColor = UIColor.greenColor()
button.setTitle("Button", forState: UIControlState.Normal)
button.addTarget(self, action: Selector("Action:"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error:: \(error)")
}
}
extension ViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
mapView.removeAnnotations(mapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
extension ViewController : MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.pinTintColor = UIColor.orangeColor()
pinView?.canShowCallout = true
pinView?.rightCalloutAccessoryView = UIButton(type: UIButtonType.DetailDisclosure) as UIButton
return pinView
}
func mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == annotationView.rightCalloutAccessoryView {
print("Disclosure Pressed!")
}
}
}
you can pass annotation or lat long to another view controller while performing segue or push new controller. If you are using segue then you can use prepare for segue method.
Second thing you can make a custom class for mapview. and can put some properties in it like annotation or lat long then create object of this class and set this property so according that properties this class return map view (make method accordingly). you can use class in many viewcontroller not only in just two.
Update as per comment :
refer this link to know how to push new view controller.
and refer this storyboard segue tutorial. Actually it concepts and not possible to explain here everything if you have some error in code then i can solve here but for learn whole concepts you should follows different tutorials and notes. do research. google it you will find many links.
hope this will help :)