didSelect for MKAnnotationView not firing - ios

I have troubles to make a custom annotation at a MapView selectable. I was trying several ways, without success:
Using didSelect of MKMapViewDelegate
Using a UIButton which is added to the AnnotationView
Using UITapGestureRecognizer for the ImageView, which should be used for the annotation
You can find all these approaches commented in the code below:
override func viewDidLoad() {
super.viewDidLoad()
// some more initialization code
for item in items {
let annotation = IdentifiedMKPointAnnotation() // has only one additional property named "id"
annotation.coordinate = CLLocationCoordinate2D(latitude: item.spots[0].latitude!, longitude: item.spots[0].longitude!)
annotation.id = i
self.mapView.addAnnotation(annotation)
i += 1
}
}
// MARK: - MapViewDelegate
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let item = self.items[(annotation as! IdentifiedMKPointAnnotation).id!]
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "")
//annotationView.canShowCallout = false
//annotationView.isEnabled = false
let imageView = UIImageView()
imageView.frame.size = CGSize(width: 40, height: 40)
imageView.layer.cornerRadius = 20
imageView.layer.masksToBounds = true
imageView.af_setImage(withURL: URL(string: item.images.count > 0 ? item.images[0] : item.image!)!)
imageView.contentMode = .scaleAspectFill
//let gr = UIGestureRecognizer(target: self, action: #selector(annotationSelected(_:)))
//imageView.addGestureRecognizer(gr)
//imageView.isUserInteractionEnabled = true
//let button = UIButton()
//button.frame.size = CGSize(width: 40, height: 40)
//button.setImage(#imageLiteral(resourceName: "play_arrow"), for: .normal)
//button.addTarget(self, action: #selector(annotationSelected(_:)), for: .touchUpInside)
annotationView.addSubview(imageView)
//annotationView.addSubview(button)
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
performSegue(withIdentifier: "showDetail", sender: self.items[(view.annotation as! IdentifiedMKPointAnnotation).id!])
view.setSelected(false, animated: false)
}
func annotationSelected(_ sender: UIImageView) {
performSegue(withIdentifier: "showDetail", sender: self.items[((sender.superview as! MKAnnotationView).annotation as! IdentifiedMKPointAnnotation).id!])
}
Any ideas why the action(s) isn't/aren't firing?

If the annotation's title is blank, didSelect delegate is not called. Try the following code.
let annotation = IdentifiedMKPointAnnotation()
annotation.title = "title"

Just found the solution based on the following post: https://stackoverflow.com/a/28671680/6010489
It's enough to set height and width of the annotationview:
annotationView.frame.size.height = 40
annotationView.frame.size.width = 40

For some reason I had the same problem when mapView's delegate was set in Storyboard (Xcode 10.2, Swift 5.0). So this was not working:
However, when I removed the connection and did it in code instead it worked fine:
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
A Swift 5 bug maybe? Storyboard solution works with Objective-C.

Related

Mapkit with LongPressGestureRecognizer working intermittently

I have a very simple setup, where I have a MapKit, and I'd like to be able to drop pin to it everytime the user do a long press on the map.
It works ok for the first pin. But if the user tried another long press (on another location), then the gesture is not recognized. The third time will work, the fourth time doesnt work, etc (every odd attempt will get recognized).
I have a sample project that demonstrate this:
https://github.com/alexwibowo/SimpleLocationMarker
Basically, my view controller looks like below:
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView! {
didSet{
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.addNewAnnotation(recognizer:)))
gestureRecognizer.minimumPressDuration = 0.5
mapView.addGestureRecognizer(gestureRecognizer)
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func addNewAnnotation(recognizer: UILongPressGestureRecognizer){
if (recognizer.state != .began) {
return
}
let touchPoint = recognizer.location(in: mapView)
let wayCoords = mapView.convert(touchPoint, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.title = "New"
annotation.coordinate = wayCoords
mapView.addAnnotation(annotation)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "locationMarkerIdentifier"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.annotation = annotation
view.canShowCallout = true
view.isDraggable = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
return view
}
}
I'm sure it is a very simple mistake I've done!
First, set its delegate to self.
#IBOutlet weak var mapView: MKMapView! {
didSet{
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.addNewAnnotation(recognizer:)))
gestureRecognizer.minimumPressDuration = 0.5
mapView.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.delegate = self
}
}
Second,
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}

Swift: MapView calloutAccessoryControlTapped index

class MapViewController: UIViewController, MKMapViewDelegate, HomeModelProtocol {
var feedItems: NSArray = NSArray()
var selectedLocation : LocationModel = LocationModel()
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let initialLocation = CLLocation(latitude: 45.444958, longitude: 12.328463)
centerMapLocation(location: initialLocation)
mapView.delegate = self
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
feedItems = items
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
let regionRadus: CLLocationDistance = 1000
func centerMapLocation(location: CLLocation){
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadus, regionRadus)
mapView.setRegion(coordinateRegion, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//checkLocationAuthorizationStatus()
displayLocations()
}
func displayLocations(){
let i = feedItems.count
var x = 0
while x<i{
let item: LocationModel = feedItems[x] as! LocationModel
var poiCoodinates = CLLocationCoordinate2D()
poiCoodinates.latitude = CDouble(item.latitude!)!
poiCoodinates.longitude = CDouble(item.longitude!)!
let pin: MKPointAnnotation = MKPointAnnotation()
pin.coordinate = poiCoodinates
self.mapView.addAnnotation(pin)
pin.title = item.name
pin.subtitle = item.address
x = x+1
}
//return loc
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = MKMarkerAnnotationView(annotation: selectedLocation as? MKAnnotation, reuseIdentifier: "pin")
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.leftCalloutAccessoryView = UIButton(type: .detailDisclosure)
return view
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print(control.tag)
selectedLocation = feedItems[0] as! LocationModel
performSegue(withIdentifier: "InformationSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get reference to the destination view controller
let detailVC = segue.destination as! InformationViewController
// Set the property to the selected location so when the view for
// detail view controller loads, it can access that property to get the feeditem obj
detailVC.selectedLocation = selectedLocation
}
}
This is my code.
I want to display the Location in the next Viewcontroller.
I need to get the index at feeditems[].
How can i get the index in:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
So how do i get the index, which Button is tapped. There are many objects that are placed in the map.
Thank you for help and sorry for my bad english, hope you guys understand me.
1.Define Sublass MKPointAnnotation.
class MyPointAnnotation: MKPointAnnotation {
var feedItem: LocationModel
}
2.Set MyPointAnnotation.feedItem to feedItem.
let item: LocationModel = feedItems[x] as! LocationModel
var poiCoodinates = CLLocationCoordinate2D()
poiCoodinates.latitude = CDouble(item.latitude!)!
poiCoodinates.longitude = CDouble(item.longitude!)!
let pin: MyPointAnnotation = MyPointAnnotation()
pin.coordinate = poiCoodinates
pin.feedItem = item // Important!
self.mapView.addAnnotation(pin)
3.Get feedItem in calloutAccessoryControlTapped delegate method.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if let pin = view.annotation as? MyPointAnnotation {
print(pin.feedItem)
}
}
Sublass MKAnnotation add index property / object from feedItems array to the class and
see custom class MyAnnotation implemented there in swift customPinAnnotationButton
this idea but now i have only objective -c version
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"wqwqwqwqwqw . . .");
MyAnnotation*ann = view.annotation;
NSLog(#"nammemmeme : %#",ann.weatherItem);
[self performSegueWithIdentifier:#"showDetails" sender:ann.weatherItem];
}
1.Define subclasses for Annotation
class PointAnnotation: MKPointAnnotation {
var indexAnnotation = 0
}
2.Mapview Delegate
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isMember(of: MKUserLocation.self) {
return nil
}
let identifier = "myAnnotation"
var annotationView: MKAnnotationView?
annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.image = UIImage(named:"Golf Courses.png")
annotationView?.canShowCallout = true
let callButton = UIButton(type: .detailDisclosure)
annotationView?.rightCalloutAccessoryView = callButton
annotationView?.sizeToFit()
} else {
annotationView!.annotation = annotation
}
}
3.Callaccessory button taped go to next view contoller
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
let obj = kStoryboardShops.instantiateViewController(withIdentifier: "ShopDetailsViewController") as! ShopDetailsViewController
if let annotation = view.annotation as? PointAnnotation {
obj.dicDetails = arrayOfItems[annotation.indexAnnotation]
}
let nav = UINavigationController(rootViewController: obj)
self.present(nav, animated: true, completion: nil)
}
}

Transfer of Description and Image using Segue

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.

Sending picked image to another view

Im working with Mapkit. In the leftcalloutaccessory, there is a small image, thats been taken from the userlocation. In the rightcalloutaccesory, there is a button, that will make a segue, to another viewcontroller, so the user can see the image in large size.
The issue is - it is random which saved image, that will be shown on the another view.
class ImageAnnotation: MKPointAnnotation {
var image: UIImage?
}
Then in the Map view controller it looks like this
var imageAnnotations: [ImageAnnotation] = []
var sendImage: UIImageView!
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? ImageAnnotation else {
return nil
}
let identifier = "MyCustomAnnotation"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
let image = UIImage(named: "advance.png")
let button = UIButton(type: .DetailDisclosure)
button.frame = CGRectMake(0, 0, 30, 30)
button.setImage(image, forState: .Normal)
annotationView?.rightCalloutAccessoryView = button
let detailImage = UIImageView(frame: CGRectMake(0, 0, 50, 50))
detailImage.image = annotation.image
sendImage.image = annotation.image
annotationView?.leftCalloutAccessoryView = detailImage
return annotationView
}
It works. Its showing the right image in every annotation. But "pickedimage", can be every saved images.
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
for annotation in imageAnnotations {
mapView.addAnnotation(annotation)
}
gettingData()
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
{
if (view.annotation is MKPointAnnotation) {
print("Clicked annotation ")
if control == view.rightCalloutAccessoryView {
sendImage.image = UIImage(named: "advance.png")
}
}
}
func buttonPressed (sender: UIButton!) {
let currentImage: UIImage = sender.imageForState(.Normal)!
sendImage.image = currentImage
self.performSegueWithIdentifier("ShowLargeImage", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowLargeImage" {
let goToImageViewController = segue.destinationViewController as! ImageViewController
goToImageViewController.newImage = sendImage.image
}
}
In destination view controller:
class ImageViewController: UIViewController
{
#IBOutlet weak var finalImage: UIImageView! // assume that its your imageViewname
var newImage: UIImage! // assume that it is pass Image
override func viewDidLoad() {
super.viewDidLoad()
if let img = newImage
{
finalImage.image = img
}
}
}
In Source view controller:
//create one Common ImageView
var sendImage: UIImageView!
func buttonPressed (sender: UIButton!) {
self.performSegueWithIdentifier("ShowLargeImage", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowLargeImage" {
let goToImageViewController = segue.destinationViewController as! ImageViewController
goToImageViewController.newImage = sendImage.image // directly pass the image no need of converson
}
}
at the same time add here also
let detailImage = UIImageView(frame: CGRectMake(0, 0, 50, 50))
detailImage.image = annotation.image
sendImage.image = annotation.image
annotationView?.leftCalloutAccessoryView = detailImage
updated answer
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
{
if (view.annotation is MKPointAnnotation) {
print("Clicked annotation ")
if control == view.rightCalloutAccessoryView {
sendImage.image = UIImage(named: "advance.png")
}
}
}

Can i use the same map with the same location in another View Controller?

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 :)

Resources