Delegate not working when trying to redirect on a map

I am creating an app where you have different pins on and when the user clicks on the search bar at top it will show him a view controller with a table view which has all the pins on the map (the map is on ViewController.swift). When I tap a table view cell I want my application to redirect the user to the specific pin on the map view.
Unfortunately I get an error every time I tap a cell.

“Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value” on the line:
selectionDelegate!.didTapChoice(self, coordinates: location)

Code is as follows:

SearchViewController.swift

import UIKit
import MapKit

protocol SelectionCoordinateDelegate: AnyObject {
    func didTapChoice(_ searchController: SearchViewController, coordinates: CLLocationCoordinate2D)
}

final class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, CLLocationManagerDelegate {
    
    
    weak var selectionDelegate: SelectionCoordinateDelegate?
    
    private var tableView: UITableView!
    var filteredOffices = [OfficeStats]()
    var filteredNames = [String]()
    
    lazy var VC = ViewController()
    let searchBar = UISearchBar()
   
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setUpNavBar()
        searchBar.delegate = self
        filteredOffices = VC.officesCopy
        
        VC.retrieveNameCopy() { (success, filteredOffices) in
            if success {
                self.VC.sortList()
            } else {
                print("unsuceess")
            }
        }
        let barHeight = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
        let displayWidth: CGFloat = self.view.frame.width
        let displayHeight: CGFloat = self.view.frame.height
        
        tableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.dataSource = self
        tableView.delegate = self
        self.view.addSubview(tableView)
    }
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.text = ""
        searchBar.endEditing(true)
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        filteredOffices = []

        if searchText == "" {
            filteredOffices = VC.officesCopy
        }
        else{
            for offices in VC.officesCopy {
                
                if offices.name.lowercased().contains(searchText.lowercased()){
                    filteredOffices.append(offices)
                }
            }
        }
        self.tableView.reloadData()
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print(filteredOffices.count)
        return filteredOffices.count
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(filteredOffices[indexPath.row])
        let geoPoint = filteredOffices[indexPath.row].coordinates
        let location = CLLocationCoordinate2D(latitude: geoPoint.latitude, longitude: geoPoint.longitude)

        print(selectionDelegate)
        selectionDelegate!.didTapChoice(self, coordinates: location)
        print(location)
        dismiss(animated: true)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        searchBar.becomeFirstResponder()
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "cell")
        cell.textLabel?.text = filteredOffices[indexPath.row].name
        cell.detailTextLabel?.text = filteredOffices[indexPath.row].location
        return cell
    }
    
    func setUpNavBar() {
        searchBar.sizeToFit()
        searchBar.searchBarStyle = .prominent
        searchBar.placeholder = "Search"
        searchBar.tintColor = UIColor.lightGray
        searchBar.barTintColor = UIColor.lightGray
        navigationItem.titleView = searchBar
        searchBar.isTranslucent = true
    }
}

Show us your delegate class

This is what my ViewController.swift looks like

import UIKit
import MapKit
import CoreLocation
import Parse

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UISearchBarDelegate {
    
    @IBOutlet weak var LiveMap: MKMapView!
    private weak var searchController: SearchViewController?

    var officesCopy = [OfficeStats]()
    var officesNameCopy = [String]()

    var annotationName = ""
    
    let queriesClass = Queries()
    let manager = CLLocationManager()
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        self.hideKeyboardWhenTappedAround()
        
        manager.desiredAccuracy = kCLLocationAccuracyReduced // Affects battery life
        manager.delegate = self
        manager.requestWhenInUseAuthorization()
        manager.startUpdatingLocation()
        loadPins()
    }
    
    func setUpNavBar() {
            let searchBar = UISearchBar()
            searchBar.delegate = self
            searchBar.sizeToFit()
            searchBar.searchBarStyle = .minimal
            searchBar.placeholder = "Kërko"
            searchBar.tintColor = UIColor.lightGray
            searchBar.barTintColor = UIColor.lightGray
            navigationItem.titleView = searchBar
            searchBar.isTranslucent = true
        }
        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
            self.present(UINavigationController(rootViewController: SearchViewController()), animated: false, completion: nil)
        }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let searchController = SearchViewController()        
        searchController.selectionDelegate = self
        setUpNavBar()

        self.LiveMap.delegate = self
        self.navigationItem.largeTitleDisplayMode = .never

        retrieveNameCopy() { (success, offices) in
                    if success {
//                        self.officesNameCopy.sort {$0 < $1}
                    } else {
                        print("unsuceess")
                    }
                }
        
        loadPins()
        bashkiaReposition(self)
        
        NotificationCenter.default
            .addObserver(self,
                         selector: #selector(statusManager),
                         name: .flagsChanged,
                         object: nil)
        updateUserInterface()
    }
    
    func retrieveNameCopy(completion: @escaping (_ success: Bool, _ elements: [String]) -> Void) {
        let queries = PFQuery(className: "Drejtori")
        queries.fromLocalDatastore()
        queries.findObjectsInBackground { (object, error) in
            if let error = error {
                // The query failed
                print(error.localizedDescription)
                completion(false, [])
            } else if let object = object {
                // The query succeeded with a matching result
                for i in object{
                    self.officesCopy.append(OfficeStats(name: i["name"] as! String, phone: i["phone"] as? String ?? "", location: i["location"] as? String ?? "", website: i["website"] as? String ?? "", coordinates: PFGeoPoint(latitude: (i["coordinates"] as! PFGeoPoint).latitude, longitude: (i["coordinates"] as! PFGeoPoint).longitude), email: i["email"] as? String ?? "", short_description: i["short_description"] as? String ?? "", long_description: i["long_description"] as? String ?? ""))
                    self.officesNameCopy.append(i["name"] as! String)
                }
                completion(true, self.officesNameCopy)
            } else {
                completion(true, [])
                // The query succeeded but no matching result was found
            }
        }
    }
    
    func sortList() { // should probably be called sort and not filter
        officesCopy.sort { $0.name < $1.name } // sort the fruit by name
    }
    
    func updateUserInterface() {
        switch Network.reachability.status {
        case .unreachable:            
            print("Unreachable")
        case .wwan:
            queriesClass.updateQueries()
            queriesClass.saveAllQueriesLocally()
        case .wifi:
            queriesClass.updateQueries()
            queriesClass.saveAllQueriesLocally()
        }
        print("Reachability Summary")
        print("Status:", Network.reachability.status)
        print("HostName:", Network.reachability.hostname ?? "nil")
        print("Reachable:", Network.reachability.isReachable)
        print("Wifi:", Network.reachability.isReachableViaWiFi)
    }
    @objc func statusManager(_ notification: Notification) {
        updateUserInterface()
    }
    
    func loadPins() {
        let localQuery = PFQuery(className: "Drejtori")
        localQuery.fromLocalDatastore()
        localQuery.findObjectsInBackground { (objects, error) -> Void in
            if error == nil {
                if let returnedobjects = objects {
                    for object in returnedobjects {
                        //Declaring coordinates
                        let userGeoPoint = object["coordinates"] as? PFGeoPoint
                        let annotation = MKPointAnnotation()
                        annotation.title = object["name"] as? String
                        annotation.subtitle = object["location"] as? String

                        //Adding coordinates and pins to the map
                        annotation.coordinate = CLLocationCoordinate2D(latitude: userGeoPoint?.latitude ?? 0, longitude: userGeoPoint?.longitude ?? 0)
                        self.LiveMap.addAnnotation(annotation)
                        self.LiveMap.delegate = self
                    }
                }
            }
        }
    }
    
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let renderer = MKPolylineRenderer(overlay: overlay)
             renderer.strokeColor = UIColor.systemBlue
             renderer.lineWidth = 5.0
             return renderer
    }
    
    func showRouteOnMap(pickupCoordinate: CLLocationCoordinate2D, destinationCoordinate: CLLocationCoordinate2D) {
            let request = MKDirections.Request()
            request.source = MKMapItem(placemark: MKPlacemark(coordinate: pickupCoordinate, addressDictionary: nil))
            request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinate, addressDictionary: nil))
        
            request.requestsAlternateRoutes = true
            request.transportType = .automobile

            let directions = MKDirections(request: request)

            directions.calculate { [unowned self] response, error in
                guard let unwrappedResponse = response else { return }
                
                //for getting just one route
                if let route = unwrappedResponse.routes.first {
                    //show on map
                    self.LiveMap.addOverlay(route.polyline)
                    //set the map area to show the route
                    self.LiveMap.setVisibleMapRect(route.polyline.boundingMapRect, edgePadding: UIEdgeInsets.init(top: 80.0, left: 20.0, bottom: 100.0, right: 20.0), animated: true)
                }
            }
        }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.first {
            manager.stopUpdatingLocation()
            render(location)
        }
    }
    
    func render(_ location: CLLocation) {
        let coordinate = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
        let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
        let _ = MKCoordinateRegion(center: coordinate, span: span)
        //LiveMap.setRegion(region, animated: true)
        self.LiveMap.showsUserLocation = true
    }
        
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        print(searchText)
    }
        
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if annotation is MKUserLocation {
            return nil
        }
        
        let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "customannotation")
        annotationView.image = UIImage(named:"logoBashkia")
        annotationView.canShowCallout = true
        
        let button = UIButton(type: UIButton.ButtonType.detailDisclosure) as UIButton // button with info sign in it
        annotationView.rightCalloutAccessoryView = button
        
        return annotationView
    }
        
    func indexOfTap() -> Int{
        var indexOfOffice = 0
        for (index, element) in officesNameCopy.enumerated(){
            if element == annotationName {
                indexOfOffice = index
            }
        }
        return indexOfOffice
    }
    
    func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
        if control == view.rightCalloutAccessoryView{
            print(view.annotation?.title!! as Any) // your annotation's title
            //Perform a segue here to navigate to another viewcontroller
            annotationName = view.annotation?.title!! ?? ""
            performSegue(withIdentifier: "showPopoverDetail", sender: self)
        }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? OfficeDetailViewController {
            destination.office = officesCopy[(indexOfTap())]
        }
    }
    
    @IBAction func relocation(_ sender: Any) {
        let region: MKCoordinateRegion = MKCoordinateRegion(center: LiveMap.userLocation.coordinate, span: LiveMap.region.span)
            LiveMap.setRegion(region, animated: true)
    }
    
    @IBAction func bashkiaReposition(_ sender: Any) {
        let query = PFQuery(className: "Drejtori")
        query.whereKey("name", equalTo:"Bashkia Tiranë")
        query.fromLocalDatastore()
        query.findObjectsInBackground { (objects: [PFObject]?, error: Error?) in
            if let error = error {
                // Log details of the failure
                print(error.localizedDescription)
            } else if let objects = objects {
                //The find succeeded.
                print("Successfully retrieved \(objects.count) scores.")
                for object in objects {
                    //print(object.objectId as Any)
                    //Setting default location for Bashkia Tiranës
                    let userGeoPoint = object["coordinates"] as! PFGeoPoint
                        let location = CLLocationCoordinate2D(latitude: userGeoPoint.latitude, longitude:userGeoPoint.longitude)
                        let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
                        //Redirecting the map to Bashkia Tiranë
                    if self.LiveMap != nil{
                        self.LiveMap.setRegion(region, animated: true)
                    }
                }
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

extension UIViewController {
    func hideKeyboardWhenTappedAround() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        tap.cancelsTouchesInView = true
        view.addGestureRecognizer(tap)
    }

    @objc func dismissKeyboard() {
        view.endEditing(true)
    }
}

extension ViewController: SelectionCoordinateDelegate {
    func didTapChoice(_ searchController: SearchViewController, coordinates: CLLocationCoordinate2D) {
        let latCoord = coordinates.latitude
        let longCoord = coordinates.longitude
        let location = CLLocationCoordinate2D(latitude: latCoord, longitude:longCoord)
        let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
        self.LiveMap.setRegion(region, animated: true)
    }
    
    
}

Try changing your protocol type annotation from “AnyObject” to “class”.

That didn’t do anything either :confused:

Have you set a breakpoint on your code where your delegate property is being set? See if it contains a non nil value.

Yeah it does it says:
“selectionDelegate = (Map.SelectionCoordinateDelegate?) nil”

I would comment out all the unnecessary code and leave just the protocol delegate design pattern to eliminate any other effects

I’m new to iOS development btw.
And this is part of the protocol delegate so I’m not really sure removing anything would have any effect. I believe the way I am connecting my ViewController and SearchViewController is the problem :confused:
Something must be missing from one part or the other and I really can’t tell what.

Like I said, you need to be sure that your protocol delegate pattern is working first then start adding the rest of the code. You can’t be sure nothing else is affecting it unless you start commenting out.

I see, can you help me guide on how I can make sure the pattern is working?

Just do what I said!

This topic was automatically closed after 166 days. New replies are no longer allowed.