I am relatively new to Swift language. I know how to launch mapkit in my app with added annotations but I want to add a pin without the standard red pin but with a custom image that i have. Is there a simple way to make it happen? Thanks
You didn't add any code into your question so I am giving you an example code.
Use this delegate method viewForAnnotation for custom pin.
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
}
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier("pin")
if pinView == nil {
pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: "pin")
pinView!.canShowCallout = true
//Set your image here
pinView!.image = UIImage(named: "mappin")
var calloutButton = UIButton.buttonWithType(.DetailDisclosure) as! UIButton
pinView!.rightCalloutAccessoryView = calloutButton
}
else {
pinView!.annotation = annotation
}
return pinView
}
Hope it will help.
Related
I am having trouble setting a pin color for my map annotation. I have a function in my MapView viewcontroller that pulls from an array from another view controller and depending on the case of the type, I want different pin colors for the map view. I am not sure how I can add the pin color information to the annotation within this switch statement. My understanding of annotations is rather weak so any explanations, rather than the solution itself, are greatly appreciated.
class ColorPointAnnotation: MKPointAnnotation {
var pinColor: UIColor
init(pinColor: UIColor) {
self.pinColor = pinColor
super.init()
}
}
func add(newLocation location_one:[String:Any]) {
let momentaryLat = (location_one["latitude"] as! NSString).doubleValue
let momentaryLong = (location_one["longitude"] as! NSString).doubleValue
var annotation = MKPointAnnotation()
switch location_one["type"] {
case "Tomorrow":
print("The pin color is red")
annotation = ColorPointAnnotation(pinColor: UIColor.red)
case "Next Week":
print("The pin color is green")
annotation = ColorPointAnnotation(pinColor: UIColor.green)
default:
print("The pin color is purple")
annotation = ColorPointAnnotation(pinColor: UIColor.purpleColor)
}
annotation.title = location_one["title"] as? String
annotation.coordinate = CLLocationCoordinate2D(latitude: momentaryLat as CLLocationDegrees, longitude: momentaryLong as CLLocationDegrees)
DispatchQueue.main.async {
self.map.addAnnotation(annotation)
}
self.map.centerCoordinate = annotation.coordinate
}
func mapView(_ map: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// if (annotation is MKUserLocation) {
// return nil
// }
let identifier = "pinAnnotation"
var annotationView = map.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
let colorPointAnnotation = annotation as! ColorPointAnnotation
annotationView?.pinTintColor = colorPointAnnotation.pinColor
}
// else {
// annotationView?.annotation = annotation
//
// }
// map.showAnnotations(map.annotations, animated: true)
return annotationView
}
You need move your switch statement into the viewForAnnotation delegate method. Here you when you return a pin, you can customize the color and then return it.
Like so :
annotation.pinColor = MKPinAnnotationColorGreen;
Updated Answer :
You can subclass MKPointAnnotation, and add a property that stores the type of annotation.
When you create an annotation in your add method, set the property to what ever type of pin it is.
Now in the viewForAnnotation method, mapkit will give the annotation of your type. Look at the set property and determine which color pin to return.
Let me know if you want see some code.
I want to change the standard annotation callout view for the user location annotation in Swift.
Therefore I created a custom XIB and a custom class. In the mapkit delegate I load the respective annotations. The custom annotations appear for every map annotation added as expected but not for the "current user location". Here the standard one is being displayed still. How do I need to change my code so the standard one will be exchanged to the one below?
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
var identifier = ""
if (annotation is MKUserLocation) {
identifier = "userLocation"
let annotationInfo:[String:AnyObject] = ["featureTypeId": GISFeatureType.AnnotationCurrentPosition.rawValue]
if let userAnnotation = MapAnnotation(annotationInfo: annotationInfo) {
userAnnotation.title = "Aktueller Standort"
let annotationView = MapAnnotationView(annotation: userAnnotation, reuseIdentifier: identifier)
annotationView.canShowCallout = true
return annotationView
}
}
// Annotation is not user location
guard let annotation = annotation as? MapAnnotation, let featureTypeId = annotation.featureTypeId else {
return nil
}
identifier = "MapAnnotation"
let annotationView = MapAnnotationView(annotation: annotation, reuseIdentifier: identifier)
switch (featureTypeId) {
case .AnnotationIncident:
identifier = "MapAnnotationIncidentDetailView"
let detailView = UIView.loadFromNibNamed(identifier) as! MapAnnotationIncidentDetailView
detailView.incident = annotation.incident
annotationView.detailCalloutAccessoryView = detailView
case .AnnotationAED:
identifier = "MapAnnotationAEDDetailView"
let detailView = UIView.loadFromNibNamed(identifier) as! MapAnnotationAEDDetailView
detailView.aed = annotation.aed
annotationView.detailCalloutAccessoryView = detailView
case .AnnotationCurrentPosition:
identifier = "MapAnnotationCurrentDetailView"
let detailView = UIView.loadFromNibNamed(identifier) as! MapAnnotationCurrentDetailView
annotationView.detailCalloutAccessoryView = detailView
default:
return nil
}
annotationView.canShowCallout = true
return annotationView
}
Im setting a custom image for the user location on a map like this:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isEqual(mapView.userLocation) {
let identifier = "User"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil{
annotationView = CustomPointAnnotation(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: userImage)
return annotationView
}
return nil
}
Ive change the image property, how can I tell the map to update the MKAnnotationView?
This solutions works for me:
let userLocation = mapView.view(for: mapView.userLocation)
userLocation?.image = UIImage(named: "newImage")
userLocation?.isHidden = true
userLocation?.isHidden = false
One of my answers - https://stackoverflow.com/a/57868438/7505665
I had similar problem and solution in my case was this code
self.mapView.showsUserLocation = false
self.mapView.showsUserLocation = true
I think you want to remove the annotations and then re-add them and that will force the map to update. Editing an existing annotation directly doesn't force the map to redraw.
My app currently has a local search which adds annotations for the search results. I want to set it up where when you select the annotation and click the call out button, it will open in the Maps application with directions to the annotation from the current device location. I'm having a few problems with this.
First of all, my call out button on the annotation does not appear. Secondly, I don't think I am detecting the selected annotation correctly.
Here is my code for the search and selected annotation:
func performSearch() {
matchingItems.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchText.text
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler({(response:
MKLocalSearchResponse!,
error: NSError!) in
if error != nil {
println("Error occured in search: \(error.localizedDescription)")
} else if response.mapItems.count == 0 {
println("No matches found")
} else {
println("Matches found")
for item in response.mapItems as! [MKMapItem] {
println("Name = \(item.name)")
println("Phone = \(item.phoneNumber)")
self.matchingItems.append(item as MKMapItem)
println("Matching items = \(self.matchingItems.count)")
var annotation = MKPointAnnotation()
var coordinates = annotation.coordinate
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
self.mapView.addAnnotation(annotation)
}
}
})
}
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!,
calloutAccessoryControlTapped control: UIControl!) {
if self.mapView.selectedAnnotations?.count > 0 {
if let selectedLoc = self.mapView.selectedAnnotations[0] as? MKAnnotation {
println("Annotation has been selected")
let currentLoc = MKMapItem.mapItemForCurrentLocation()
let mapItems = NSArray(objects: selectedLoc, currentLoc)
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
MKMapItem.openMapsWithItems([selectedLoc, currentLoc], launchOptions: launchOptions)
}
}
}
Any help will be appreciated, thanks in advance.
For the first issue:
A callout button needs to be set explicitly in the viewForAnnotation delegate method (default red pins don't have one). Here's a simple example of one possible implementation:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.pinColor = .Purple
//next line sets a button for the right side of the callout...
pinView!.rightCalloutAccessoryView = UIButton.buttonWithType(.DetailDisclosure) as! UIButton
}
else {
pinView!.annotation = annotation
}
return pinView
}
For the second issue:
First, in calloutAccessoryControlTapped, the annotation is directly accessible using view.annotation so using the selectedAnnotations array in there is unnecessary.
Next, openMapsWithItems expects an array of MKMapItem objects but in the array you are passing ([selectedLoc, currentLoc]), selectedLoc is not an MKMapItem -- it is just some object that implements MKAnnotation.
Running this code will result in a crash with this error:
-[MKPointAnnotation dictionaryRepresentation]: unrecognized selector sent to instance
when the Maps app tries to use selectedLoc as if it was an MKMapItem.
Instead, you need to create an MKMapItem from the selectedLoc annotation. This can be done by first creating an MKPlacemark from the annotation using MKPlacemark(coordinate:addressDictionary:) and then creating an MKMapItem from the placemark using MKMapItem(placemark:).
Example:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!,
calloutAccessoryControlTapped control: UIControl!) {
let selectedLoc = view.annotation
println("Annotation '\(selectedLoc.title!)' has been selected")
let currentLocMapItem = MKMapItem.mapItemForCurrentLocation()
let selectedPlacemark = MKPlacemark(coordinate: selectedLoc.coordinate, addressDictionary: nil)
let selectedMapItem = MKMapItem(placemark: selectedPlacemark)
let mapItems = [selectedMapItem, currentLocMapItem]
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
MKMapItem.openMapsWithItems(mapItems, launchOptions:launchOptions)
}
I have different classes (MKAnnotation subclass) that represent map annotations.
I load them on the map and it's fine but when I move or zoom map pins starts to loosing their image.
When I pan map viewForAnnotation is called but pin that is BluePin class for example not anymore get in it's if block and as a result it renders the last annotation from viewForAnnotation with default pin image (green).
UPDATE
I just realised that code actually get in each IF properly but never get in nested IF so this line of code make some issues:
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("blue")
if (annotationView == nil) { ... after zoom/pan never get here }...
What can be the issue here:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
return nil
}
if(annotation.isKindOfClass(BluePin)){
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("blue")
if (annotationView == nil) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "blue")
annotationView.canShowCallout = true;
annotationView.image = UIImage(named: "blue")
return annotationView
}
} else if(annotation.isKindOfClass(RedPin)){
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("red")
if (annotationView == nil) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "red")
annotationView.canShowCallout = true;
annotationView.image = UIImage(named: "red")
return annotationView
}
}
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier("def") as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "def")
pinView!.pinColor = .Green
}
else {
pinView!.annotation = annotation
}
return pinView
}
The dequeueReusableAnnotationViewWithIdentifier method will return a previously-created view (if any) that is not currently being used for display (this is the whole point of using this method so that you get view re-use).
When the method does find a view that can be re-used, it will return that view and so the result will be non-nil.
When you zoom/pan the map, some annotations go out of view and new ones come into view. The ones that come into view can now re-use the views of the annotations no longer visible. So dequeueReusableAnnotationViewWithIdentifier returns a non-nil result.
The current code isn't handling the case where dequeueReusableAnnotationViewWithIdentifier returns non-nil for the blue and red pins and so execution continues with the next statement after the big if block which is var pinView = mapView... which then creates the default green pin view.
The code needs to be modified to handle the cases where dequeueReusableAnnotationViewWithIdentifier returns non-nil for the blue and red pins:
if(annotation.isKindOfClass(BluePin)) {
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("blue")
if (annotationView == nil) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "blue")
annotationView.canShowCallout = true;
annotationView.image = UIImage(named: "blue")
} else {
//handle blue view re-use...
annotationView.annotation = annotation
}
//move return to after the if-else...
return annotationView
}
else if(annotation.isKindOfClass(RedPin)) {
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("red")
if (annotationView == nil) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "red")
annotationView.canShowCallout = true;
annotationView.image = UIImage(named: "red")
} else {
//handle red view re-use...
annotationView.annotation = annotation
}
//move return to after the if-else...
return annotationView
}