I'd like to replace the default MKPointAnnotation logo with a custom one.
So I wrote this code in my MapViewController:
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var myMap: MKMapView!
let locationManager = CLLocationManager()
var monPin:CustomPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
//Mark: - Authorization
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
myMap.delegate = self
myMap.mapType = MKMapType.standard
myMap.showsUserLocation = true
let location = CLLocationCoordinate2D(latitude: 43.2885108, longitude:5.3855545000000124)
let center = location
let region = MKCoordinateRegionMake(center, MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025))
myMap.setRegion(region, animated: true)
monPin = CustomPointAnnotation()
monPin.pinCustomImageName = "mapIcon"
monPin.coordinate = location
monPin.title = "Mon titre"
monPin.subtitle = "mon Sous titre"
pinAnnotationView = MKPinAnnotationView(annotation: monPin, reuseIdentifier: "pin")
myMap.addAnnotation(pinAnnotationView.annotation!)
}
//MARK: - Custom Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier:reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
let customPointAnnotation = annotation as! CustomPointAnnotation
annotationView?.image = UIImage(named: customPointAnnotation.pinCustomImageName)
return annotationView
}
Where monPin.pinCustomImageName = "mapIcon" refer to my xassets
But it only display a classic MKPointAnnotation picture (this one: https://i.stack.imgur.com/pD82c.png)
**And I think the problem is that: ** the function mapView is not called anywhere because I tried a simple print("hello") in it and it does not appear in the console and even if I erase all this function code, it does not change anything to my app.
That's why I'm wondering how I could face this problem.
I don't understand 100% of my code because I took it from a kind stack overflow user, I mean I understand all the code except the mapView() part.
I believe Xcode has shown a warning if you have created a new project with default settings. (You should not ignore any of the warnings.)
Instance method 'mapView(mapView:viewForAnnotation:)' nearly matches
optional requirement 'mapView(_:viewFor:)' of protocol
'MKMapViewDelegate'
This line of your code:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
does not implement any of the protocol methods declared in MKMapViewDelegate in Swift 3.
And the quick fix feature (choose the first one) fixes it as:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
If your Xcode does not help you fix if, you may need to make it by yourself.
Anyway, with the fix shown above, the method should be called.
Related
My iOS map view shows an initial annotation. I want the user to be able to click on the map and for the annotation to move to the spot where the user clicked, but to retain the non-coordinate information in the annotation (e.g. title, subtitle). The code below works, but intermittently the title and subtitle are not showing (see animation and that the name "Spot Name" sometimes doesn't show). It's unclear to me why this is occurring. If I add print statements to print spot.name and spot.title after mapView.addAnnotation, the String values are there & remain unchanged in the MKAnnotation conforming class. Also, when I click in the marker, the proper title and subtitle show in the callout, even if they weren't showing in the annotation. Grateful for any advice/corrections. Thanks!
import UIKit
import MapKit
class SpotDetailViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
var spot: Spot! // Spot class conforms to NSObject & MKAnnotation
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
spot = Spot()
spot.coordinate = CLLocationCoordinate2D(latitude: 42.334709, longitude: -71.170061)
spot.name = "Spot Name"
spot.address = "Spot Address, Spot Town, Spot State"
// Set initial region
let regionDistance: CLLocationDistance = 250
let region = MKCoordinateRegionMakeWithDistance(spot.coordinate, regionDistance, regionDistance)
mapView.setRegion(region, animated: true)
mapView.addAnnotation(self.spot)
}
#IBAction func mapViewTapped(_ sender: UITapGestureRecognizer) {
let annotationView = mapView.view(for: mapView.annotations[0])
let touchPoint = sender.location(in: mapView)
guard !(annotationView?.frame.contains(touchPoint))! else {
return
}
let newCoordinate: CLLocationCoordinate2D = mapView.convert(touchPoint, toCoordinateFrom: mapView)
spot.coordinate = newCoordinate
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotation(self.spot)
mapView.setCenter(spot.coordinate, animated: true)
}
}
extension SpotDetailViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifer = "Marker"
var view: MKMarkerAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifer) as? MKMarkerAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifer)
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
return view
}
}
I'm developing an OSX app.
I've subclassed a MKAnnotation:
import Foundation
import MapKit
class LocationAnnotation: NSObject, MKAnnotation {
var image: NSImage?
var title: String?
var coordinate: CLLocationCoordinate2D
init(image anImage: NSImage?, title aTitle: String, coordinate aCoordinate: CLLocationCoordinate2D) {
self.image = anImage
self.title = aTitle
self.coordinate = aCoordinate
}
}
In my NSViewController subclass, I've added a MKMapViewDelegate and in Interface Builder I've added a MKMapView and set its delegate to NSViewController.
To find out what's wrong, I'm adding three locations in my ViewDidLoad method, into an array. I'm adding those annotations to my map in ViewDidAppear. I plan to move that to a background thread when I figure out what's wrong.
import Cocoa
import MapKit
class ShowLocationsViewController: NSViewController, MKMapViewDelegate {
#IBOutlet var locationMap: MKMapView!
private var myLocationArray: [LocationAnnotation] = []
private var myRegion: MKCoordinateRegion!
//#MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
myLocationArray = []
myRegion = nil
let locLondon = LocationAnnotation(image: nil, title: "London", coordinate: CLLocationCoordinate2DMake(51.522617, -0.139371))
let locWembley = LocationAnnotation(image: nil, title: "Wembley", coordinate: CLLocationCoordinate2DMake(51.555909, -0.279600))
let locGreenwich = LocationAnnotation(image: nil, title: "Greenwich", coordinate: CLLocationCoordinate2DMake(51.476572, -0.001596))
myLocationArray.append(locLondon)
myLocationArray.append(locWembley)
myLocationArray.append(locGreenwich)
myRegion = MKCoordinateRegion.init(center: locLondon.coordinate, span: MKCoordinateSpanMake(0.20, 0.20)) // 20km span
}
override func viewDidAppear() {
locationMap.addAnnotations(myLocationArray)
locationMap.setRegion(myRegion, animated: true)
}
}
When using delegate's method viewFor annotation: I print out the title of each annotation in a log and all three of them are listed. In another delegate's method didAdd I'm printing out the number of annotations of my map in a log and it prints out three. But on my map, there are no annotations displayed. I tried panning and zooming with no success. Region gets properly set and displayed though.
//#MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
print(locationMap.annotations.count, views.count)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is LocationAnnotation {
let annotationIdentifier = "Location"
var annotationView = locationMap.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) as! MKPinAnnotationView?
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
}
annotationView!.canShowCallout = true
print(annotationView!.annotation!.title ?? "no view")
return annotationView
} else {
print("no annotation")
return nil
}
}
When I try the same code in an iOS app, everything works. I assume there is nothing wrong with my delegate because methods are properly called.
I would appreciate any help you can give me.
I figured out the problem, the way the window is being called.
I created a variable mainWindowController in my AppDelegate, then I subclassed my main NSViewController so that I could hook up AppDelegate to it.
let appDelegate = NSApp.delegate as! AppDelegate
appDelegate.mainWindowController = self
Afterwards I used this code to call my window which was errornous:
let mapViewController = mainWindowController?.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier.init(rawValue: "ShowLocationsID")) as! ShowLocationsViewController
mainWindowController?.contentViewController?.presentViewControllerAsModalWindow(mapViewController)
I changed that in a way that I created a segue in my storyboard and called the following code which worked and my pins did show:
performSegue(withIdentifier: NSStoryboardSegue.Identifier(rawValue: "ShowLocations"), sender: self)
I have two pin annotations on my map. I created a info button to perform the segue. But the button appears only on one annotation. Can anyone help me please? For the annotations I created a function that I put into the viewdidload method.
Here`s the code:
import UIKit
import MapKit
class MapVC: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var locManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
meinkartenPunkt1()
meinkartenPunkt2()
locManager = CLLocationManager()
locManager.requestWhenInUseAuthorization()
// Do any additional setup after loading the view.
mapView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func mapView(_ mapView: MKMapView,didUpdate userLocation: MKUserLocation){
mapView.region.center=userLocation.coordinate
mapView.showAnnotations(mapView.annotations, animated: true)
}
func meinkartenPunkt1() {
let breite: CLLocationDegrees = 8.737653
let länge: CLLocationDegrees = 47.504333
let Koordinaten = CLLocationCoordinate2DMake(länge, breite)
let Span = MKCoordinateSpanMake(0.01, 0.01)
let Region = MKCoordinateRegionMake(Koordinaten, Span)
mapView.setRegion(Region, animated: true)
let Stecknadel = MKPointAnnotation()
Stecknadel.coordinate = Koordinaten
Stecknadel.title = "Heiligberg"
mapView.addAnnotation(Stecknadel)
}
func meinkartenPunkt2() {
let breite: CLLocationDegrees = 8.734345
let länge: CLLocationDegrees = 47.508456
let Koordinaten = CLLocationCoordinate2DMake(länge, breite)
let Span = MKCoordinateSpanMake(0.01, 0.01)
let Region = MKCoordinateRegionMake(Koordinaten, Span)
mapView.setRegion(Region, animated: true)
let Stecknadel = MKPointAnnotation()
Stecknadel.coordinate = Koordinaten
Stecknadel.title = "Kanti Rychenberg"
mapView.addAnnotation(Stecknadel)
}
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
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: "bookDetails", sender: self)
}
}
}
When looking at your source code, I cannot directly see an answer, however I see an immediate problem. You do some important setup on the pinView, while it is nil. To be honest, this code certainly crashes, if it is ever reached:
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
Luckily for you, pinView never is nil however, as the mapView creates the pinView successfully. What you probably want to do, is to set the calloutButton, if it is not set. So instead of if pinView == nil you write:
if pinView?.rightCalloutAccessoryView == nil {
// Initialize the rightCalloutAccessoryView here
}
Tip: In general you should avoid force unwraps with ! as it leads to crashes. Try to use optional chaining with ? instead.
I am making a Swift application that uses MKPointAnnotations, and I recently ran into an issue where I needed to store metadata in my annotations, so I created the custom class below:
class BRETTFAnnotation: MKPointAnnotation {
var tag: Int64
var name: String
init(lat : Double, lon:Double, t : Int64, n: String) {
self.tag = t
self.name = n
super.init()
self.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
}
My MKAnnotationView viewfor MKAnnotation method is shown below:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let newAnnotation = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "reuse")
newAnnotation.canShowCallout = true
let right = self.button(title: "Yes")
right?.addTarget(self, action: #selector(clickedToConfirmNewPoint), for: .touchUpInside)
newAnnotation.rightCalloutAccessoryView = right
let left = self.button(title: "No")
left?.addTarget(self, action: #selector(clickedToCancelNewPoint), for: .touchUpInside)
newAnnotation.leftCalloutAccessoryView = left
return newAnnotation
}
The problem I am running into is when ever I click on my custom BRETTFAnnotation (which I add to my MKMapView) nothing happens. When I was just using the MKPointAnnotation (instead of the BRETTFAnnotation) when I clicked on the map the two buttons on the MKAnnotationView would show. I am trying to get the MKPinAnnotationView to show on touch using my BRETTFAnnotation instead of the MKPointAnnotation.
How can I continue to use my custom annotation and show the callout when the user clicks on the annotation at the same time?
Edit 1: Since it is probably useful the code below is how I make the annotation and add it to the mapView.
let location = gestureRecognizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
print("adding lat,long \(coordinate.latitude),\(coordinate.longitude)")
lastPoint = BRETTFAnnotation(lat: coordinate.latitude, lon: coordinate.longitude, t: 1, n: "")
let annotationView = MKPinAnnotationView(annotation: lastPoint, reuseIdentifier: "reuse")
mapView.addAnnotation(lastPoint)
I fix this problem by making my BRETTFAnnotation a subclass of NSObject and MKAnnotation instead of MKPointAnnotation. Doing this allowed my custom class to receive user interaction and show the callouts.
When you use your own MKAnnoation you can handle your actions in didSelect. Just implement the following code.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let yourAnnotation = view.annotation as? BRETTFAnnotation {
//handle your meta data or/and show UIViews or whatever
}
}
with
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
//getting called when you tap on map or on another annotation (not the selected annotation before)
//hide UIViews or do whatever you want
}
That does work for me:
class ViewController: UIViewController, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
print("didSelect")
if let annoation = view.annotation as? MyAnnoation {
print("metatag \(annoation.metaTag)")
}
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
print("didDeselect")
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
let annotation = MyAnnoation(n: "name", m: "metaTag")
annotation.coordinate = CLLocationCoordinate2D(latitude: 50.0, longitude: 8.0)
mapView.addAnnotation(annotation)
}
}
class MyAnnoation: MKPointAnnotation {
var name: String?
var metaTag: String?
init(n: String, m: String) {
self.name = n
self.metaTag = m
}
}
I am trying to perform a Segue between two UI Views I have correctly typed the identifier for the segue as can be seen Here
This is my function, with the same identifier "no" but when the button is clicked in the simulator it simply shows This (It looks like it is showing the stack in bottom left?)
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
performSegue(withIdentifier: "no", sender:self)
}
}
I have attached my full code incase further analysis is needed. Thanks for your help.
View Controller:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate,CLLocationManagerDelegate {
#IBOutlet weak var MapView: MKMapView!
let manager = CLLocationManager()
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)
let 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)
}
}
}
Any help appreciated.
See the solid blue bar in the gutter in your screen shot?
It is a breakpoint. When the path of execution reaches a breakpoint that is active (solid blue like this), we pause there. That is what a breakpoint is for.
If you don't want that to happen, drag the breakpoint out of the gutter.