Why doesn't my MKPointAnnotation appear when the view loads? - ios

In my app, the user saves some data, including a map coordinate. In my code, a pin is dropped at the saved map coordinate. Here is my code-
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
// Data loading
itemNameTextField.delegate = self
itemDescriptionLabel.delegate = self
itemLocationTextView.delegate = self
// Press recognizer
if let item = item {
itemNameTextField.text = item.itemName
itemDescriptionLabel.text = item.itemDescription
itemLocationTextView.text = item.itemPlace
let dropPin = MKPointAnnotation()
dropPin.coordinate = mapView.convert(item.mapPoint, toCoordinateFrom: mapView)
dropPin.title = "Location of \(item.itemName)"
self.mapView.addAnnotation(dropPin)
print("Set the location of item pin to \(String(describing: dropPin.coordinate))")
}
// Styles
itemInfoView.layer.cornerRadius = 3
itemInfoView.layer.shadowColor = UIColor(red:0/255.0, green:0/255.0, blue:0/255.0, alpha: 1.0).cgColor
itemInfoView.layer.shadowOffset = CGSize(width: 0, height: 1.75)
itemInfoView.layer.shadowRadius = 1.7
itemInfoView.layer.shadowOpacity = 0.45
itemLocationView.layer.cornerRadius = 3
itemLocationView.layer.shadowColor = UIColor(red:0/255.0, green:0/255.0, blue:0/255.0, alpha: 1.0).cgColor
itemLocationView.layer.shadowOffset = CGSize(width: 0, height: 1.75)
itemLocationView.layer.shadowRadius = 1.7
itemLocationView.layer.shadowOpacity = 0.45
locationAdressTextview.layer.cornerRadius = 2
locationAdressTextview.layer.shadowColor = UIColor(red:0/255.0, green:0/255.0, blue:0/255.0, alpha: 1.0).cgColor
locationAdressTextview.layer.shadowOffset = CGSize(width: 0, height: 1.50)
locationAdressTextview.layer.shadowRadius = 1.6
locationAdressTextview.layer.shadowOpacity = 0.3
}
I know that the app does save the pin coordinates as a CGPoint, and I know that it converts it from a CGPoint to a CLLocationCoordinate2D, because of the print statements I placed. However, when the screen loads, the print statement shows a valid coordinate, but there is no pin on the map, and I get no errors. Can somebody please help me? Thanks!

Whenever the map should display an annotation the MKMapViewDelegate method viewForAnnotation is called so you have to implement that method and return a view according to your needs
Sample code:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "myAnnotationView")
// configure the view
return annotationView
}

Related

Change a custom MKAnnotationView when it is selected

I have a MKMapView with several different types of MKAnnotationViews on it. Chiefly, I have a PricedAnnotationView, which is a fairly complicated MKAnnotationView with a UILabel and some other views. The label can have different text in it, based off of the original MKAnnotation that it is associated with.
class PricedAnnotationView: MKAnnotationView {
static let ReuseID = "pricedAnnotation"
let labelContainingView: UIView = {
let containingView = UIView()
containingView.backgroundColor = .blue
containingView.layer.cornerRadius = 3
containingView.translatesAutoresizingMaskIntoConstraints = false
return containingView
}()
let label: UILabel = {
let label = UILabel(frame: CGRect.zero)
label.textColor = .white
label.font = ViewConstants.Text.BodyFontBold
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let containingView: UIView = {
let v = UIView(frame:CGRect.zero)
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
let triangle = UIView(frame: CGRect(x: 0, y: 0, width: 9, height: 9))
triangle.translatesAutoresizingMaskIntoConstraints = false
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 5.5, y: 11))
path.addLine(to: CGPoint(x: 11, y: 0))
path.close()
let triangleLayer = CAShapeLayer()
triangleLayer.path = path.cgPath
triangleLayer.fillColor = UIColor.blue.cgColor
triangle.layer.addSublayer(triangleLayer)
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.canShowCallout = false
self.frame = labelContainingView.frame
labelContainingView.addSubview(label)
containingView.addSubview(labelContainingView)
containingView.addSubview(triangle)
self.addSubview(containingView)
// Get the label correctly inset in the labelContainingView
label.topAnchor.constraint(equalTo: labelContainingView.topAnchor, constant: 3).isActive = true
label.leadingAnchor.constraint(equalTo: labelContainingView.leadingAnchor, constant: 6).isActive = true
label.trailingAnchor.constraint(equalTo: labelContainingView.trailingAnchor, constant: -6).isActive = true
label.bottomAnchor.constraint(equalTo: labelContainingView.bottomAnchor, constant: -3).isActive = true
// The triangle.topAnchor purposefully puts the triangle a bit under the label. In testing, while moving around
// the map, a little gap would appear between the label and the triangle. This change fixes that. The triangle
// was made two pixels bigger so that it would appear to be the same size.
triangle.topAnchor.constraint(equalTo: labelContainingView.bottomAnchor, constant: -2).isActive = true
triangle.centerXAnchor.constraint(equalTo: labelContainingView.centerXAnchor).isActive = true
triangle.widthAnchor.constraint(equalToConstant: 11).isActive = true
triangle.heightAnchor.constraint(equalToConstant: 11).isActive = true
containingView.topAnchor.constraint(equalTo: labelContainingView.topAnchor).isActive = true
containingView.leadingAnchor.constraint(equalTo: labelContainingView.leadingAnchor).isActive = true
containingView.trailingAnchor.constraint(equalTo: labelContainingView.trailingAnchor).isActive = true
containingView.bottomAnchor.constraint(equalTo: triangle.bottomAnchor).isActive = true
}
/// - Tag: DisplayConfiguration
override func prepareForDisplay() {
super.prepareForDisplay()
displayPriority = .required
guard let annotation = annotation as? MyAnnotation else { return }
if case let .priced(price, currencyCode) = annotation.status {
label.text = StringFormatter.formatCurrency(amount: price, currencyCode: currencyCode)
}
self.layoutIfNeeded() // Calculate the size from the constraints so we can know the frame.
self.frame = containingView.frame
self.centerOffset = CGPoint(x: 0, y: -(containingView.frame.height / 2)) // Center point should be where the triangle points
}
}
Every other annotation is much simpler and just uses one of two UIImage to support itself. I use two different reuse Ids with those simpler MKAnnotationView so I don't have to keep setting the image; I don't know if that is a best practice or not. Here is a sample of how I do it:
private let unpricedAnnotationClusterId = "unpriced"
private let unpricedAnnotationReuseID = "unpricedAnnotation"
func createUnpricedAnnotation(mapView: MKMapView, annotation: MKAnnotation) -> MKAnnotationView {
let annotationView: MKAnnotationView
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: unpricedAnnotationReuseID) {
annotationView = dequeuedAnnotationView
annotationView.annotation = annotation
} else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: unpricedAnnotationReuseID)
annotationView.image = UIImage(bundleAsset: "map_pin_blue")
annotationView.centerOffset = CGPoint(x: 0, y: -(annotationView.frame.height / 2)) // Center point should be where the pin points
annotationView.clusteringIdentifier = unpricedAnnotationClusterId
}
return annotationView
}
When the user taps on an annotation in the map, I have an information window below the map populate with information about the selected item. That works fine. I would like it if the annotation view changed though so it was clear as to where the item is that is being displayed. I would be okay with just setting it to an image. However, I can't find any example of how to do that; most examples just change the image. When I try that with PricedAnnotationView the previous label does not go away, although the two simpler annotations work fine. I'm also unsure how changing the image would affect the reuse Ids that I am using. I don't see a way to manually change the reuse identifier.
My MKMapViewDelegate has these functions:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if allowSelection {
if let item : MyAnnotation = view.annotation as? MyAnnotation {
...
// display selected item in information view
...
}
view.image = UIImage(bundleAsset: "map_pin_red")
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? MyAnnotation else {
return nil
}
switch annotation.status {
case .notPriced:
return createUnpricedAnnotation(mapView: mapView, annotation: annotation)
case .priced(_, _):
return PricedAnnotationView(annotation: annotation, reuseIdentifier: PricedAnnotationView.ReuseID)
default:
return createErrorAnnotation(mapView: mapView, annotation: annotation)
}
}
If I could go the route of just setting the image, how do I change it back after the user has selected something else. Would I need to recreate PricedAnnotationView from scratch if it was previously one of those annotation? What would that do to the reuse Ids and the reuse queue?
When I have just changed the image, the view for PricedAnnotationView does not actually go away, and is just moved to the side as the image is shown beneath.
Ideally, I could do something to trigger a call to mapView(_:viewFor:) and have some intelligence in there to remember if the item is selected or now, but I have not found anything in my research to show how to do that. (It is not completely trivial because the isSelected property does not seem to be set on the MKAnnotationView that I would have just dequeued. No surprise there.)
Any suggestions as to how to handle this would be greatly appreciated.
I've played around with this, and here is what I've done to get around this issue:
I stopped using different reuse ids for the two types of image annotations. I created this function to manage that:
func createImageAnnotation(mapView: MKMapView, annotation: MKAnnotation, imageString: String) -> MKAnnotationView {
let annotationView: MKAnnotationView
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: imageAnnotationReuseID) {
annotationView = dequeuedAnnotationView
annotationView.annotation = annotation
} else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: imageAnnotationReuseID)
}
annotationView.image = UIImage(bundleAsset: imageString)
annotationView.centerOffset = CGPoint(x: 0, y: -(annotationView.frame.height / 2)) // Center point should be where the pin points
annotationView.clusteringIdentifier = imageAnnotationClusterId
return annotationView
}
I then added to the select and deselect calls so that the PricedAnnotationView is hidden and the image set when appropriate. Like this:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if allowSelection {
if let item : MyAnnotation = view.annotation as? MyAnnotation {
...
// display selected item in information view
...
view.image = UIImage(bundleAsset: selectedAnnotationImage)
if case .priced(_,_) = item.rateStatus {
(view as! PricedAnnotationView).containingView.isHidden = true
}
view.centerOffset = CGPoint(x: 0, y: -(view.frame.height / 2)) // Center point should be where the pin points
}
}
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
guard let annotation = view.annotation as? MyAnnotation else { return }
switch annotation.rateStatus {
case .notPriced:
view.image = UIImage(bundleAsset: unpricedAnnotationImage)
case .priced(_, _):
view.image = nil
if let myView = (view as? PricedAnnotationView) {
myView.containingView.isHidden = false
view.frame = myView.containingView.frame
view.centerOffset = CGPoint(x: 0, y: -(myView.containingView.frame.height / 2)) // Center point should be where the triangle points
}
default:
view.image = UIImage(bundleAsset: errorAnnotationImage)
}
}
This seems to mostly work, with the only quirk being that when the PricedAnnotationView is changed to the image, the image briefly and visibly shrinks from the old frame size to the correct frame size. However, if I do not set the frame, then the PricedAnnotationView is oddly offset.

Mapbox iOS - Custom annotations are invisible

I'm trying to create simple custom map annotations on Mapbox iOS (5.5.0) by implementing MGLMapViewDelegate protocol. Annotations are actually added to the map, but they are invisible. I can predict its coordinate and open its callout, so, I know it's there.
I have even tried copying the exact codes given on Mapbox examples to get the same result. Can it be a lack of some framework elements?
My code:
class MainViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Set the map’s size, style, center coordinate, zoom level, and tint color.
let mapView = MGLMapView(frame: view.bounds)
let coordinates = CLLocationCoordinate2D(latitude: 40.4561, longitude: 49.7263)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.styleURL = MGLStyle.darkStyleURL(withVersion: 9)
mapView.setCenter(coordinates, zoomLevel: 15, animated: false)
//mapView.userTrackingMode = .followWithHeading
//mapView.showsUserHeadingIndicator = true
view.addSubview(mapView)
mapView.delegate = self
let marker = MGLPointAnnotation()
marker.coordinate = coordinates
marker.title = "Area 51"
marker.subtitle = "There is no way in!"
mapView.addAnnotation(marker)
}
// Use the default marker. See also: our view annotation or custom marker examples.
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
guard annotation is MGLPointAnnotation else {
return nil
}
let annotationAvailable = isAnnotationAvailable(annotation)
let reuseIdentifier = "\(annotationAvailable)"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if (annotationView == nil) {
annotationView = MGLAnnotationView()
annotationView = CustomAnnotationView(reuseIdentifier: reuseIdentifier)
annotationView!.bounds = CGRect(x: 0, y: 0, width: 40, height: 40)
if (!annotationAvailable) {
annotationView!.backgroundColor = UIColor(displayP3Red: 255, green: 0, blue: 0, alpha: 1)
}
else {
annotationView!.backgroundColor = UIColor(displayP3Red: 0, green: 255, blue: 0, alpha: 1)
}
}
return annotationView
}
// Allow callout view to appear when an annotation is tapped.
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func isAnnotationAvailable(_ annotation: MGLAnnotation) -> Bool {
return false
}
}
class CustomAnnotationView: MGLAnnotationView {
override func layoutSubviews() {
super.layoutSubviews()
// Use CALayer’s corner radius to turn this view into a circle.
layer.cornerRadius = bounds.width / 2
layer.borderWidth = 2
layer.borderColor = UIColor.white.cgColor
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Animate the border width in/out, creating an iris effect.
let animation = CABasicAnimation(keyPath: "borderWidth")
animation.duration = 0.1
layer.borderWidth = selected ? bounds.width / 4 : 2
layer.add(animation, forKey: "borderWidth")
}
}
The result:
Mapbox Screenshot

MapView annotation showing image and title

I am developing an app in which I should present MapView annotations showing an image and a title. The following View Controller Swift code shows a default pin image with the desired title right below:
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
// Define zoom
let deltaLat: CLLocationDegrees = 1.0
let deltaLon: CLLocationDegrees = 1.0
// Define location of center coordinates
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(-15.3, -47.0)
// Define area to be viwed
let areaVisual: MKCoordinateSpan = MKCoordinateSpanMake(deltaLat, deltaLon)
let region = MKCoordinateRegionMake(location, areaVisual)
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = "SDKP"
mapView.addAnnotation(annotation)
// Show map region defined by the above parameters
mapView.setRegion(region, animated: true)
}
/*
// Show an image for annotation
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
annotationView.image = imageLiteral(resourceName: "AnnotationImage")
return annotationView
}
*/
}
This is the MapView I get with this:
When I un-comment the view for annotation method, I get the desired annotation image, but not the title:
Any ideas on how can I get both the image and title at the same time for the annotation?
I found a solution in which I use the func imageFromLabel(_:) in code below to extend UIImage to create an image from a label text which is the title for the annotation. Then I combine the annotation image with this title image through the func combineImageAndTitle(_:_:). Finally, this combined image is showed by the mapView delegate method viewFor annotation.
Since I am still a beginner with Swift, I am not sure if it is the best way to do that. But this solution is working fine for me.
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
// Define zoom
let deltaLat: CLLocationDegrees = 1.0
let deltaLon: CLLocationDegrees = 1.0
// Define location of center coordinates
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(-15.3, -47.0)
// Define area to be viwed
let areaVisual: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: deltaLat, longitudeDelta: deltaLon)
let region = MKCoordinateRegion(center: location, span: areaVisual)
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = "SDKP"
mapView.addAnnotation(annotation)
// Show map region defined by the above parameters
mapView.setRegion(region, animated: true)
}
// Delegate method for mapView
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
let imageForAnnotation = #imageLiteral(resourceName: "BaseImage")
let annotationTitle = (annotation.title ?? "") ?? ""
//annotationView.image = imageForAnnotation
annotationView.image = combineImageAndTitle(image: imageForAnnotation, title: annotationTitle)
return annotationView
}
/// Combine image and title in one image.
func combineImageAndTitle(image: UIImage, title: String) -> UIImage {
// Create an image from ident text
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
label.numberOfLines = 1
label.textAlignment = .center
label.textColor = UIColor.black
label.text = title
let titleImage = UIImage.imageFromLabel(label: label)
// Resulting image has a 100 by 100 size
let contextSize = CGSize(width: 100, height: 100)
UIGraphicsBeginImageContextWithOptions(contextSize, false, UIScreen.main.scale)
let rect1 = CGRect(x: 50 - Int(image.size.width / 2), y: 50 - Int(image.size.height / 2), width: Int(image.size.width), height: Int(image.size.height))
image.draw(in: rect1)
let rect2 = CGRect(x: 0, y: 53 + Int(image.size.height / 2), width: Int(titleImage.size.width), height: Int(titleImage.size.height))
titleImage.draw(in: rect2)
let combinedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return combinedImage!
}
}
extension UIImage {
/// Convert a label to an image
class func imageFromLabel(label: UILabel) -> UIImage {
UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0.0)
label.layer.render(in: UIGraphicsGetCurrentContext()!)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
And this is the resulting MapView.
You can use MKMarkerAnnotationView and glyphImage property. Try the following code
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: nil)
annotationView.glyphImage = UIImage(named: "Laugh")
return annotationView
}

Add a label on MGLPolygon

I have a situation where I need to draw an MGLPolygon on a map(MapBox) and I also want to give a UILabel like text on the polygon. The label has to be at the centroid of the polygon and it should be always visible. I found a code with which I can find the centroid of a given polygon, But I couldn't add a label to polygon. I have done the coding in SWIFT so swift developers please help me. Thanks in advance and Happy Coding :)
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
if let currentAnnotation = annotation as? AreaAnnotation {
let reuseIdentifier = currentAnnotation.areaTitle
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier!)
if annotationView == nil {
annotationView = MGLAnnotationView(reuseIdentifier: reuseIdentifier)
annotationView?.frame = CGRect(x: 0, y: 0, width: 120, height: 90)
annotationView!.backgroundColor = UIColor.clear
let detailsLabel:UILabel = UILabel()
detailsLabel.frame = CGRect(x: 30, y: 60, width: 60, height: 25)
detailsLabel.textAlignment = .center
detailsLabel.text = currentAnnotation.areaTitle
// detailsLabel.textColor = UIColor(red:175/255 ,green:255/255, blue:255/255 , alpha:0.75)
detailsLabel.textColor = UIColor.white
detailsLabel.font = UIFont(name: "HelveticaNeue-CondensedBlack", size: 15)
let strokeTextAttributes = [NSAttributedStringKey.strokeColor : UIColor.black, NSAttributedStringKey.strokeWidth : -5.0,] as [NSAttributedStringKey : Any]
detailsLabel.attributedText = NSAttributedString(string: titleLabel.text!, attributes: strokeTextAttributes)
detailsLabel.backgroundColor = UIColor.black.withAlphaComponent(1.0)
detailsLabel.clipsToBounds = true
detailsLabel.layer.cornerRadius = 5.0
detailsLabel.layer.borderWidth = 2.0
detailsLabel.layer.borderColor = UIColor.white.cgColor
annotationView?.addSubview(detailsLabel)
}
return annotationView
}
return nil
}
Thanks #jmkiley but I wanted to clear out that issue as fast as possible so I used this tweak, which was the exact thing I wanted.
If you have the center point of the polygon, you could use it to create a MGLPointFeature. Then create a MGLShapeSource and MGLSymbolStyleLayer with it. Provide the text to that layer. For example:
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
var mapView : MGLMapView!
var line: MGLPolyline?
override func viewDidLoad() {
super.viewDidLoad()
mapView = MGLMapView(frame: view.bounds)
view.addSubview(mapView)
mapView.delegate = self
let coords = [
CLLocationCoordinate2D(latitude: 38.0654, longitude: -88.8135),
CLLocationCoordinate2D(latitude: 41.7549, longitude: -88.8135),
CLLocationCoordinate2D(latitude: 41.7549, longitude: -83.1226),
CLLocationCoordinate2D(latitude: 38.0654, longitude: -83.1226)
]
let polygon = MGLPolygon(coordinates: coords, count: UInt(coords.count))
mapView.addAnnotation(polygon)
}
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
let point = MGLPointFeature()
point.coordinate = CLLocationCoordinate2D(latitude: 40.0781, longitude: -85.6714)
let source = MGLShapeSource(identifier: "point-source", features: [point], options: nil)
style.addSource(source)
let layer = MGLSymbolStyleLayer(identifier: "point-layer", source: source)
layer.text = MGLStyleValue(rawValue: "Polygon A")
style.addLayer(layer)
}
}

swift custom map class

I am learning Swift and want to create a subclass of MKMapKit to encapsulate some specific functionality, like checking distance between two points and creating custom annotations and separate all the map code into one class.
I have created a class:
class GameMapViewController: MKMapView, MKMapViewDelegate{...}
I initiate the class in code in the main view controller (and adding it as a subview to a view on the storyboard so I can control where it is more easily):
gameMap = GameMapViewController(container: mapViewHolder)
which sets everything up ok and all works EXCEPT for when I want to trigger a segue from a custom annotation:
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {...}
The didSelectAnnotationView gets called when I tap on an annotation callout but nothing has the method performSegueWithIdentifier that I am looking for, that all the solutions to similar questions suggest I should be using....
(I have tried putting a MapKit View onto the storyboard and changing its class to use GameMapViewController but none of the init functions get fired)
I am guessing its something to with how I am initialising my custom class?
MainViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
....
// Create the game map
gameMap = GameMapViewController(container: mapViewHolder)
mapViewHolder.addSubview(gameMap)
...
}
GameMapViewController.swift:
import UIKit
import MapKit
class GameMapViewController: MKMapView, MKMapViewDelegate{
var spanQuestion:MKCoordinateSpan = MKCoordinateSpanMake(180, 180)
var spanAnswer:MKCoordinateSpan = MKCoordinateSpanMake(180, 180)
var hasUserCityLocationGuess: Bool = false
var containingView: UIView
override init(){
println ("GameMapViewController init")
containingView = UIView()
super.init(frame: CGRect(x: 0, y: 0, width: 1000, height: 1000))
self.delegate=self
var latDeltaAnswer:CLLocationDegrees = 50
var lngDeltaAnswer:CLLocationDegrees = 50
spanAnswer = MKCoordinateSpanMake(latDeltaAnswer, lngDeltaAnswer)
var latDeltaQuestion:CLLocationDegrees = 180
var lngDeltaQuestion:CLLocationDegrees = 180
spanQuestion = MKCoordinateSpanMake(latDeltaQuestion, lngDeltaQuestion)
}
required init(coder aDecoder: NSCoder) {
containingView = UIView()
super.init(coder: aDecoder)
self.delegate = nil
println ("GameMapViewController init with decoder")
}
convenience init(container: UIView) {
println ("GameMapViewController convenience")
self.init()
self.delegate = self
containingView = container
}
func mapViewDidFinishLoadingMap(mapView: MKMapView!) {
println("mapViewDidFinishLoadingMap")
}
func mapViewWillStartLoadingMap(mapView: MKMapView!) {
self.frame = CGRect (x: 0, y: 0, width: containingView.frame.width, height: containingView.frame.height)
self.contentMode = UIViewContentMode.ScaleAspectFill
superview?.sizeToFit()
var guessPlaceRecognizer = UILongPressGestureRecognizer(target: self, action: "guessPlace:")
guessPlaceRecognizer.minimumPressDuration = 1.0
mapView.addGestureRecognizer(guessPlaceRecognizer)
mapView.mapType = MKMapType.Satellite
}
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKCircle {
var circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.strokeColor = UIColor.redColor()
circleRenderer.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.1)
circleRenderer.lineWidth = 1
//userOverlayCircleRender = circleRenderer
return circleRenderer
} else {
return nil
}
}
func guessPlace(gestureRecognizer:UIGestureRecognizer){
let guessPlaceFirst = NSUserDefaults.standardUserDefaults().boolForKey("guess_place_preference")
if guessPlaceFirst {
var touchPoint = gestureRecognizer.locationInView(self)
var newCoord:CLLocationCoordinate2D = self.convertPoint(touchPoint, toCoordinateFromView: self)
var userAnnotation = UserPointAnnotation()
userAnnotation.coordinate = newCoord
self.addAnnotation(userAnnotation)
var getLat: CLLocationDegrees = newCoord.latitude
var getLon: CLLocationDegrees = newCoord.longitude
var circleCenter: CLLocation = CLLocation(latitude: getLat, longitude: getLon)
addRadiusCircle(circleCenter)
hasUserCityLocationGuess = true
}
}
func showCity() {
let location = CLLocationCoordinate2D(latitude: (currentCity["latitude"]! as CLLocationDegrees), longitude: (currentCity["longitude"]! as CLLocationDegrees))
let region:MKCoordinateRegion = MKCoordinateRegionMake(location, self.spanAnswer)
let city: String = currentCity["city"]! as String
let conditions: String = currentCity["description"] as String
let country: String = currentCity["country"]! as String
let address = "\(city), \(country)"
let cityAnnotation = CityPointAnnotation()
cityAnnotation.title = address
cityAnnotation.subtitle = "\(conditions)"
cityAnnotation.coordinate = location
self.setRegion(region, animated: true)
self.addAnnotation(cityAnnotation)
self.selectAnnotation(cityAnnotation, animated: true)
}
func cityInfoClick(sender:UIButton){
//sender.performSegueWithIdentifier("segueCityWebView")
}
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
// Handle any custom annotations.
if annotation is CityPointAnnotation {
// Try to dequeue an existing pin view first.
let reuseId = "CityPointAnnotationView"
var annotationView = self.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
annotationView.image = UIImage(named: "marker.png")
annotationView.rightCalloutAccessoryView = UIButton.buttonWithType(.InfoDark) as UIButton
annotationView.canShowCallout = true
return annotationView;
} else {
annotationView.annotation = annotation
}
return annotationView
}
return nil;
}
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
println("didSelectAnnotationView")
}
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
println("calloutAccessoryControlTapped1")
///////////////////
// I want to do a segue here
// but nothing has the method performSegueWithIdentifier (self, mapView, control....)
///////////////////
}
func resetMap(){
self.removeAnnotations(self.annotations)
self.removeOverlays(self.overlays)
var region:MKCoordinateRegion = MKCoordinateRegionMake(self.centerCoordinate, spanQuestion)
self.setRegion(region, animated: true)
hasUserCityLocationGuess = false
}
func addRadiusCircle(location: CLLocation){
var radius = NSUserDefaults.standardUserDefaults().doubleForKey("guess_place_radius") as CLLocationDistance
var circle = MKCircle(centerCoordinate: location.coordinate, radius: radius )
self.removeOverlays(self.overlays)
self.addOverlay(circle)
}
func doGeoCode( cityObject:PFObject ) -> Bool {
....
}
func userCityLocationGuess(userGuessTemp:Int)->NSDictionary {
....
}
}
It's because you're confusing views and view controllers. You have a view (subclass of MKMapView, but you're naming it and trying to use it as a controller. It is also doings the job of a controller.
So, you should really have a view controller which owns and configures a map view (plain MKMapView), and then it can interact with segues.

Resources