Search code examples
swiftuibuttonmkmapviewmkannotation

my sender.tag is returning nil


I have 2 buttons on my annotations one for navigation and one for calling. But when I click the button it doesn't work at all because sender.tag returns nil. I can't find anywhere online how to set up the sender.tag for a annotation in the right way.

 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


        let annotitle = annotation.title ?? ""
        let annosubtitle = annotation.subtitle ?? ""
        let title = annotitle ?? ""
        let phone = annosubtitle ?? ""
        let type = annotation.description
        let hostel = Hostel(location: annotation.coordinate, title: title, phone: phone, type: type)
        let hostelTag = hostelsHelper.getTagForHostel(hostel)

        if pinView == nil {
            pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
            pinView!.canShowCallout = true
            pinView!.animatesDrop = true
            pinView!.pinTintColor = UIColor.black
            let mapsButton = UIButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 30, height: 30)))
            mapsButton.setBackgroundImage(UIImage(named: "Maps-icon"), for: UIControlState())
            mapsButton.tag = hostelTag
            pinView!.leftCalloutAccessoryView = mapsButton
            pinView!.leftCalloutAccessoryView?.tintColor = UIColor.black
            mapsButton.addTarget(self, action: #selector (MapViewController.didClickRouteButton(sender: )), for: .touchUpInside)


            if !phone.isEmpty {
                let callButton = UIButton(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 30, height: 30)))
                callButton.setBackgroundImage(UIImage(named: "call icon round"), for: UIControlState())
                callButton.tag = hostelTag
                pinView!.rightCalloutAccessoryView = callButton
                pinView!.rightCalloutAccessoryView?.tintColor = UIColor.black
                callButton.addTarget(self, action: #selector (MapViewController.didClickCallButton(sender: )), for: .touchUpInside)
            }
            pinView!.sizeToFit()
        }
        else {
            pinView!.annotation = annotation
        }
        return pinView
    }

    // pinButton actions
    @objc func didClickRouteButton(sender: UIButton) {
        let placemark : MKPlacemark = MKPlacemark(coordinate: hostelsHelper.getLocationFor(sender.tag))
        let mapItem:MKMapItem = MKMapItem(placemark: placemark)
        mapItem.name = hostelsHelper.getTitleFor(sender.tag)
        mapItem.phoneNumber = hostelsHelper.getPhoneFor(sender.tag)
        let launchOptions:NSDictionary = NSDictionary(object: MKLaunchOptionsDirectionsModeDriving, forKey: MKLaunchOptionsDirectionsModeKey as NSCopying)
        let currentLocationMapItem:MKMapItem = MKMapItem.forCurrentLocation()
        MKMapItem.openMaps(with: [currentLocationMapItem, mapItem], launchOptions: launchOptions as? [String : Any])
        print ("Navigation to:\(hostelsHelper.getTitleFor(sender.tag))")
    }

    @objc func didClickCallButton(sender: UIButton) -> Void {

        if let url = URL(string: "tel://\(hostelsHelper.getPhoneFor(sender.tag))") {
            UIApplication.shared.open(url, options: [:])
        }
        print ("calling:\(hostelsHelper.getTitleFor(sender.tag))")
    }

If someone could help me out would be amazing!

My hostelHelper function looks like so:

import Foundation
import MapKit

struct Hostel {
    var location: CLLocationCoordinate2D
    var title: String
    var phone: String
    var type: String
}

extension Hostel: Equatable {}

func ==(lhs: Hostel, rhs: Hostel) -> Bool {
    return lhs.location.latitude == rhs.location.latitude && lhs.location.longitude == rhs.location.longitude && lhs.title == rhs.title && lhs.phone == rhs.phone && lhs.type == rhs.type
}

class HostelsHelper {

    private var hostels: [Hostel]

    init() {

        hostels = [Hostel]()

        if let cityDetailsPath = Bundle.main.path(forResource: "Hostels", ofType: "plist") {
            guard let cityDetails = NSArray(contentsOfFile: cityDetailsPath) as? [[String: String]] else {return}

            for city in cityDetails
            {
                guard let latStr = city["latitude"] else { continue }
                guard let lonStr = city["longitude"] else { continue }
                guard let titleStr = city["title"] else { continue }
                guard let phoneStr = city["subTitle"] else { continue }
                guard let typeStr = city["type"] else { continue }

                if let lat = Double(latStr) {
                    if let lon = Double(lonStr) {
                        let coordinate = CLLocationCoordinate2DMake(lat,lon)
                        hostels.append(Hostel(location: coordinate, title: titleStr, phone: phoneStr, type: typeStr))

                    }
                }
            }
        }
    }

    func getTagForHostel(_ hostel: Hostel) -> Int {
        return hostels.index(of: hostel ) ?? -1
    }

    func getHostelsCount() -> Int {
        return hostels.count
    }

    func getPhoneFor(_ tag: Int) -> String {
        return tag != -1 ? hostels[tag].phone : ""
    }

    func getTypeFor(_ tag: Int) -> String {
        return tag != -1 ? hostels[tag].type : ""
    }

    func getTitleFor(_ tag: Int) -> String {
        return tag != -1 ? hostels[tag].title : ""
    }

    func getLocationFor(_ tag: Int) -> CLLocationCoordinate2D {
        return tag != -1 ? hostels[tag].location : kCLLocationCoordinate2DInvalid
    }
}

and the hostels are loaded from a .plist which works because the annotations are loaded into the map


Solution

  • For setting the tag to a UIButton following steps are needed.

    In my case i am setting the tag to my button in viewDidLoad() you can set it anywhere.

    @IBOutlet weak var btnAgenda: UIButton!
    
     override func viewDidLoad() {
            super.viewDidLoad()
    
            btnAgenda.tag = 5
        // Do any additional setup after loading the view.
    
        }
    
    @IBAction func btnAgendaClicked(_ sender: UIButton) {
            print(sender.tag) // 5
        }
    

    Output:

    enter image description here

    After conforming all the steps above, conform that in your line:

    let hostelTag = hostelsHelper.getTagForHostel(hostel)
    print(hostelTag)
    

    You are getting an Int value and if not do conform you convert that into a proper format. I guess it is coming nil so no tag is passed to button and you are getting nil as Output.

    In Future keep habit of checking for null conditions like this:

    if let hostelTag = hostelsHelper.getTagForHostel(hostel){
       mapsButton.tag = hostelTag as? Int
    }
    

    This will prevent you from assigning it a nil value

    Hope this helps.