Trouble creating custom MKAnnotationView - ios

I'm having a hard time displaying a custom annotation view. Specifically, I'm trying to set an image named "pin" to be the new map pin. The default pin always shows. I've been making small changes for a few hours to no avail, such as changing "pin" to "pin.png" and altering the structure of the mapView:viewFor method. Here's what I have. Could you please take a look and see if anything stands out?
Thanks for any help!
Annotation Class:
class Annotation: NSObject, MKAnnotation {
dynamic var coordinate : CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(location coord:CLLocationCoordinate2D) {
self.coordinate = coord
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
AnnotationView Class:
class AnnotationView : MKAnnotationView {
override init(annotation:MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation,
reuseIdentifier: reuseIdentifier)
let im = UIImage(named: "pin")!
self.frame = CGRect(x: 0, y: 0, width: im.size.width / 3.0 + 5, height: im.size.height / 3.0 + 5)
self.centerOffset = CGPoint(x: 0, y: -20)
self.isOpaque = false
}
required init (coder: NSCoder) {
fatalError("NSCoding not supported")
}
override func draw(_ rect: CGRect) {
let im = UIImage(named: "pin")!
im.draw(in: self.bounds.insetBy(dx: 5, dy: 5))
}
}
mapView:viewFor: Method:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var v : MKAnnotationView! = nil
let ident = "pin"
v = mapView.dequeueReusableAnnotationView(withIdentifier: ident)
if v == nil {
v = AnnotationView(annotation: annotation, reuseIdentifier: ident)
v.canShowCallout = true
}
v.annotation = annotation
return v
}
Other Relevant Methods:
#IBAction func submitDog(_ sender: Any) {
let newDog = Dog(name: newDogName.text!, score: 11, picture: image!, location: location!)
dogs.append(newDog)
print(dogs.last!)
UIView.animate(withDuration: 0.5, animations: {
}) { _ in
self.newDogView.animation = "slideUp"
self.newDogView.animate()
self.newDogView.isHidden = true
self.newDogName.text = ""
self.map.isUserInteractionEnabled = true
}
dropNewPin(locatedAt: dogs.last!.location, name: dogs.last!.name, rate: dogs.last!.score)
}
func dropNewPin(locatedAt: CLLocation, name: String, rate: Int) {
let annotation = Annotation(location: CLLocationCoordinate2D(latitude: locatedAt.coordinate.latitude, longitude: locatedAt.coordinate.longitude))
annotation.title = name
annotation.subtitle = "\(rate)/10"
self.map.addAnnotation(annotation)
}

First you need add your viewController as delegate of your map
self.mapView.delegate = self
After that I recommend you use the MKAnnotationView instead of modify and add the image with custom drawing, if you need a custom Annotation view then you need to add a xib file and your custom class as file owner and make the proper adjustments
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//to avoid make a custom Annotation view for your user location
if(annotation is MKUserLocation){
return nil
}
let ident = "pin"
var v = mapView.dequeueReusableAnnotationView(withIdentifier: ident)
if v == nil {
v = MKAnnotationView(annotation: annotation, reuseIdentifier: ident)
v?.image = UIImage(named: "pin")
v?.canShowCallout = true
}
v?.annotation = annotation
return v
}

Related

Name under custom annotation views

I have a custom annotation view, when I click on any annotation point, I can see the custom view with all information. but what I need is to see name of each industrial parks under each annotation points. now I can see only point but without names
I need to see name under points.
//MARK: MKMapViewDelegate
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation
{
return nil
}
var annotationView = self.mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
if annotationView == nil{
annotationView = AnnotationView(annotation: annotation, reuseIdentifier: "Pin")
annotationView?.canShowCallout = false
}else{
annotationView?.annotation = annotation
}
annotationView?.image = UIImage(named: "test3a")
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)
{
// 1
if view.annotation is MKUserLocation
{
// Don't proceed with custom callout
return
}
// 2
let starbucksAnnotation = view.annotation as! StarbucksAnnotation
let views = Bundle.main.loadNibNamed("CustomCalloutView", owner: nil, options: nil)
let calloutView = views?[0] as! CustomCalloutView
calloutView.starbucksName.text = starbucksAnnotation.name
calloutView.starbucksAddress.text = starbucksAnnotation.address
calloutView.starbucksPhone.text = starbucksAnnotation.phone
//
let button = UIButton(frame: calloutView.starbucksPhone.frame)
button.addTarget(self, action: #selector(CellViewController.callPhoneNumber(sender:)), for: .touchUpInside)
calloutView.addSubview(button)
calloutView.starbucksImage.image = starbucksAnnotation.image
// 3
calloutView.center = CGPoint(x: view.bounds.size.width / 2, y: -calloutView.bounds.size.height*0.52)
view.addSubview(calloutView)
mapView.setCenter((view.annotation?.coordinate)!, animated: true)
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
if view.isKind(of: AnnotationView.self)
{
for subview in view.subviews
{
subview.removeFromSuperview()
}
}
}
If your goal is to have a label under the annotation, just have your custom annotation add this subview (and have it observe changes to the title so that it can update the label).
For example:
class AnnotationView: MKAnnotationView {
static var image: UIImage = ...
private var titleObserver: NSObjectProtocol!
private let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.black.withAlphaComponent(0.25)
label.font = UIFont.preferredFont(forTextStyle: .caption1)
return label
}()
override var annotation: MKAnnotation? {
didSet { updateForNewAnnotation() }
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
image = Self.image
centerOffset = CGPoint(x: 0, y: -Self.image.size.height / 2)
configureTitleView()
updateForNewAnnotation()
}
func configureTitleView() {
addSubview(titleLabel)
NSLayoutConstraint.activate([
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
titleLabel.topAnchor.constraint(equalTo: bottomAnchor)
])
clipsToBounds = false
}
func updateForNewAnnotation() {
guard let annotation = annotation as? MKPointAnnotation else { // replace `MKPointAnnotation` with whatever class your annotations are
titleObserver = nil
titleLabel.text = nil
return
}
titleLabel.text = annotation.title
titleObserver = annotation.observe(\.title) { [weak self] annotation, _ in
self?.titleLabel.text = annotation.title
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
That yields:
Obviously, feel free to configure your label however you want, but this illustrates the basic idea of adding subview and observing changes on the annotation’s title.
As an aside, notice that I set the image inside the AnnotationView class. If you keep all configuration inside the AnnotationView class, not only is it a better separation of responsibilities, but you can then retire mapView(_:viewFor:) entirely, and replace it with a single line inside your viewDidLoad that registers the annotation view class with register(_:forAnnotationViewWithReuseIdentifier:):
mapView.register(AnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)

I can't see the annotation I added. What's wrong with it?

I don't know why the marker is not displayed..
I am using Mapbox version 6.0.0
annotationView class
// Create a subclass of MGLUserLocationAnnotationView.
class CustomUserLocationAnnotationView: MGLAnnotationView {
var imageView: UIImageView!
init(user: User) {
super.init(reuseIdentifier: user.userId)
print(">:> identifier : \(user.userId)")
self.imageView = UIImageView(frame: CGRect(x: -10, y: -10, width: 30 , height: 30))
self.imageView.layer.cornerRadius = imageView.frame.height / 2
self.imageView.clipsToBounds = true
self.imageView.image = UIImage(named: user.userProfileThumbnail ?? "profile")
self.addSubview(self.imageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
}
annotation override method
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
guard annotation is MGLPointAnnotation, let wrapTitle = annotation.title, let title = wrapTitle else {
return nil
}
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: title) {
print(">:> \(title)")
return annotationView
} else {
let view = CustomUserLocationAnnotationView(user: self.allUser[title]!)
self.annotationViews.append(view)
return view
}
}
add annotation
private func reloadAnnotationView() {
var pointAnnotations = [MGLPointAnnotation]()
for user in allUser {
let point = MGLPointAnnotation()
point.coordinate = user.value.userLocation
point.title = user.value.userId
pointAnnotations.append(point)
}
print(">:> add annotation size \(pointAnnotations.count)")
self.mapView?.addAnnotations(pointAnnotations)
}
How do I approach it problem?
It looks like your post is mostly code; please add some more details.
thanks

iOS Swift MapKit why MKPointAnnotation is draggable while a class that conforms MKAnnotation is not

i have a class that is a subclass of NSManagedObject that conform to MKAnnotation and then i use that in a MapView that dequeues some locations from CoreData
class Location: NSManagedObject , MKAnnotation {
var coordinate : CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: Double(self.latitude), longitude: Double(self.longitude))
}
var title: String? {
return self.name
}
var subtitle: String? {
return self.category
}
}
i then add the fetched objects to the MapView as MKAnnotation like that
self.MapView.addAnnotations(self.locations)
and in the viewForAnnotation i made a subclass of MKAnnotationView that has a rounded imageView
class AMKAnnotationView: MKAnnotationView {
var imageView = UIImageView()
init(annotation: MKAnnotation?, reuseIdentifier: String?, size: CGSize) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.frame = CGRectMake(0, 0, size.width, size.height)
self.commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func commonInit() {
self.imageView.frame = CGRectMake(0, 0, self.frame.width, self.frame.height)
self.imageView.layer.masksToBounds = true
self.imageView.layer.cornerRadius = self.imageView.frame.height/2
self.imageView.layer.borderWidth = 5.0
self.imageView.layer.borderColor = UIColor.whiteColor().CGColor
self.imageView.userInteractionEnabled = false
self.addSubview(imageView)
self.sendSubviewToBack(self.imageView)
}
}
then I set the annotationView to be draggable in the viewForAnnotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("pin") as? AMKAnnotationView
if annotationView == nil {
annotationView = AMKAnnotationView(annotation: annotation, reuseIdentifier: "pin", size: CGSizeMake(65, 65))
annotationView?.draggable = true
annotationView?.canShowCallout = true
let index = ... // i get the index
let location = ... // i get the current location
annotationView?.imageView.image = UIImage(named: "No Photo")
}
return annotationView
}
to make the annotationView to be draggable we should implement the didChangeDragState delegate method
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch newState {
case .Starting:
view.dragState = .Dragging
case .Ending, .Canceling:
view.dragState = .None
// then i save the changes to CoreData
}
default: break
}
}
if i try to drag the annotation on the map it doesn't work
* The solution that i don't like *
The way i got it work as of the title of this question says is to use MKPointAnnotation and i mean by that is each time i add an annotation to the map i convert to MKPointAnnotation which made me make another subclass of MKPointAnnotation so that i can keep track of the location
class AMKPointAnnotation : MKPointAnnotation {
var location : Location!
init(location:Location) {
super.init()
self.location = location
self.title = location.title
self.subtitle = location.subtitle
self.coordinate = location.coordinate
}
}
and then for adding it to the MapView
for location in self.locations {
let pointAnnotation = AMKPointAnnotation(location: location)
self.MapView.addAnnotation(pointAnnotation)
}
any one tried it before ? What am I doing wrong?
The coordinate property changes with dragging and it must be read/write and since it was a computed property with no setter the MapKit prevented the dragging for that
* here is the solution *
class Location: NSManagedObject , MKAnnotation {
var coordinate : CLLocationCoordinate2D {
set {
self.latitude = newValue.latitude
self.longitude = newValue.longitude
}
get {
return CLLocationCoordinate2D(latitude: Double(self.latitude), longitude: Double(self.longitude))
}
}
var title: String? {
return self.name
}
var subtitle: String? {
return self.category
}
}

Custom Annotation View Callout Not Showing

I have made a custom annotation and custom annotation views but for some reason the callouts are not showing.
class MyAnnotation: NSObject,MKAnnotation {
var coordinate :CLLocationCoordinate2D
var title :String?
var subtitle: String?
init(coordinate :CLLocationCoordinate2D, title :String?, subtitle :String?) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
Annotation View
class MyAnnotationView: MKAnnotationView {
override init(frame: CGRect) {
super.init(frame: frame)
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
setup()
}
func setup() {
self.canShowCallout = true
let logo = UIImage(named: "logo.png")
let size = CGSize(width: 100, height: 100)
UIGraphicsBeginImageContext(size)
logo?.drawInRect(CGRectMake(0, 0, size.width, size.height))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let logoImageView = UIImageView(image: resizedImage)
logoImageView.userInteractionEnabled = true
self.addSubview(logoImageView)
}
Populating Annotations
private func populateAnnotations() {
for location in self.locations {
let annotation = MyAnnotation(coordinate: CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude), title: location.name, subtitle: "nothing")
self.mapView.addAnnotation(annotation)
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let annotationView = MyAnnotationView(annotation: annotation, reuseIdentifier: "AnnotationView")
return annotationView
}
When I click on the custom annotation it does not show a callout.

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