0

In short, the user taps a row in a tableview (which contains data from an location) and I want to display the map with the appropriate annotation when the user taps the row. So far no luck.

I have implemented the suggestions to use a delegate pattern. Now that I have implemented it, the app keeps crashing.

I will post the code, but after browsing through dozens of stack overflow questions, I am not able to solve it. Hope you guys can help!

//MAIN CONTROLLER

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate, UISearchBarDelegate {

override func viewDidLoad() {
        super.viewDidLoad()
   }
//SearchTable initiation
    let searchTable = storyboard!.instantiateViewController(withIdentifier: "SearchTableViewController") as! SearchTableViewController

    searchController = UISearchController(searchResultsController: searchTable)
    searchController?.searchResultsUpdater = searchTable
    searchController?.hidesNavigationBarDuringPresentation = false
    searchController?.dimsBackgroundDuringPresentation = true

    searchController?.definesPresentationContext = true

    //Searchbar initiation
    let searchBar = searchController!.searchBar
    searchBar.sizeToFit()
    searchBar.keyboardAppearance = UIKeyboardAppearance.dark
    searchBar.placeholder = "Search locations"
    navigationItem.titleView = searchController?.searchBar
    searchBar.delegate = self

    searchTable.mapView = mapView
    searchTable.handleMapSearchDelegate = self
}


//Update the map
func updateMap(_ location: CLLocation)  {
    let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
    mapView.setRegion(region, animated: true)
}

//Cancel searchBar
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.text = nil
        searchBar.setShowsCancelButton(false, animated: true)
        searchBar.endEditing(true)
    }
}

//Center the map on current user's location
extension ViewController: MKMapViewDelegate {
    func centerMapOnUserLocation() {
        guard let coordinate = locationManager.location?.coordinate else { return }
        let coordinateRegion = MKCoordinateRegion(center: coordinate, latitudinalMeters: regionRadius * 2.0, longitudinalMeters: regionRadius * 2.0)
        mapView.setRegion(coordinateRegion, animated: true)
    }
}

//Zoom into selected location from search
extension ViewController: HandleMapSearch   {
    func dropPinZoomIn(placemark: MKAnnotation)    {
        updateMap(CLLocation(latitude: placemark.coordinate.latitude, longitude: placemark.coordinate.longitude))
        searchController.searchBar.text = placemark.title!
    }
}

//Zoom into selected location from selectedRow
extension ViewController: HandleSelectedLocation   {
    func dropPinAndZoomIn(placemark: MKAnnotation)    {
        updateMap(CLLocation(latitude: placemark.coordinate.latitude, longitude: placemark.coordinate.longitude))
    }
}

// TABLEVIEWCONTROLLER

import UIKit
import MapKit

protocol HandleSelectedLocation {
    func dropPinAndZoomIn(placemark: MKAnnotation)
}


//Initialize the TableViewController
class CategoriesController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    //Variables
    var mapView: MKMapView?
    var selectedItem = [MKAnnotation]()

    var handleSelectedLocationDelegate: HandleSelectedLocation? = nil

    //Outlet
    @IBOutlet var tableView: UITableView!

    //Load view
    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        tableView.delegate = self
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return location.count

    }

    //Fill cells with locations
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "allCell") ?? UITableViewCell(style: .subtitle, reuseIdentifier: "allCell")

        let locations = location[indexPath.row]
        cell.textLabel?.text = locations.title
        cell.textLabel?.textColor = UIColor.white
        cell.detailTextLabel?.text = locations.rating
        cell.detailTextLabel?.textColor = UIColor.white

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let annotation = mapView!.annotations
        let locationSelected = annotation[indexPath.row]
        handleSelectedLocationDelegate?.dropPinAndZoomIn(placemark: locationSelected)
        tableView.isHidden = true
    }
}

//SEARCHTABLEVIEWCONTROLLER

**protocol HandleMapSearch    {
    func dropPinZoomIn(placemark: MKAnnotation)
}

//Make searches appear

class SearchTableViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

    //Variables
    var mapView: MKMapView? = nil
    var matchingItems = [MKAnnotation]()
    var handleMapSearchDelegate: HandleMapSearch? = nil

    func updateSearchResults(for searchController: UISearchController) {
        guard let _ = mapView,
            let searchBarText = searchController.searchBar.text else { return  }

        matchingItems = self.mapView!.annotations.filter { annotation -> Bool in
            if annotation.title!?.range(of: searchBarText, options: .caseInsensitive) != nil {
                return true
            }

            if annotation.subtitle!?.range(of: searchBarText, options: .caseInsensitive) != nil {
                return true
            }

            return false
        }

        self.tableView.reloadData()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return matchingItems.count
    }

    //Fill tableView with location provided by search
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell(style: .subtitle, reuseIdentifier: "cell")

        let selectedItem = matchingItems[indexPath.row]
        cell.textLabel?.text = selectedItem.title!
        cell.textLabel?.textColor = UIColor.white
        cell.detailTextLabel?.text = selectedItem.subtitle!
        cell.detailTextLabel?.textColor = UIColor.white

        return cell
    }

    //Go to map and show selected annotation
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedItem = matchingItems[indexPath.row]
        handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
        dismiss(animated: true, completion: nil)
    }
}**

//CustomAnnotation

import Foundation
import MapKit

class CustomAnnotation: NSObject, MKAnnotation {
    var coordinate: CLLocationCoordinate2D
    var extraDescription: String?
    var title: String?
    var subtitle: String?
    var country: String?

    init(coordinate: CLLocationCoordinate2D) {
        self.coordinate = coordinate
    }
}

//Location struct

import Foundation
import UIKit
import MapKit

//Annotations
struct SomeLocation {
    let title: String
    let rating: String
    let description: String
    let country: String
    let latitude: Double
    let longitude: Double
}

QUESTION: Have I implemented the delegate pattern correctly? When I added the last extension, it gave me an issue with launching the app. After reversing that change, it worked but of course the delegate didn't do anything.

If you need any clarification, or extra code, please ask.

I can not seem to solve this issue, all help is appreciated greatly!

Jamie Coenen
  • 75
  • 1
  • 10
  • Try the delegate pattern. On tableview tap call a method of your main view controller passing a reference to the desired location – pckill Jan 14 '19 at 13:26
  • @pckill's pattern is a good way to go for such an implementation. If you have hard time implementing it, please share your code so far and we can help. – emrepun Jan 14 '19 at 22:28
  • @pckill Could you have a look at my updated code? I have tried to implement the delegate pattern, yet the app keeps crashing. My apologies for the extremely late response, but I had some urgent personal circumstances to deal with. – Jamie Coenen Apr 17 '19 at 18:00
  • @JamieCoenen, read about [unexpectedly found nil](https://stackoverflow.com/questions/32170456/what-does-fatal-error-unexpectedly-found-nil-while-unwrapping-an-optional-valu). And please provide [minimal, complete and verifiable example](https://stackoverflow.com/help/mcve) when asking for help. What is this `searchController` for example and where do you initialize it? – pckill Apr 18 '19 at 07:50
  • @pckill I have added the code from the `searchBar`/`searchController` (it is from type UISearchBar) and a few more pieces to indeed clarify how it works. As for the unexpectedly found nil, this only happens when I add the extension `HandleSelectedLocation`, otherwise this line executes without issue. – Jamie Coenen Apr 18 '19 at 08:36
  • @pckill Please disregard my comment, I saw in Git that I accidentally deleted the initiation of the search bar. As you can see from the updated code in **//maincontroller** it is now displayed correctly. The issue now remains with my delegate pattern. It does not work at all. I suspect it has something to do with the last block of code on **//tableviewcontroller**, but I can not really see what. – Jamie Coenen Apr 18 '19 at 10:12
  • @JamieCoenen, wehere do you set the value for `handleSelectedLocationDelegate`? – pckill Apr 18 '19 at 13:13
  • @pckill How do you mean? I was unable to set it in such a way so it goes to the correct annotation in the main controller. As I see now, the value is empty. What can I set as a value in order to let it have the desired outcome? – Jamie Coenen Apr 18 '19 at 13:18

1 Answers1

2

How about the delegate pattern?

e.g.

protocol SecondViewControllerDelegate: class {
    func didSelectRow(at: YourLocationType)
}

class SecondViewController: UITableViewController {

    weak var delegate: SecondViewControllerDelegate?

    ...

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Get the location
        delegate?.didSelectRow(at: location)
    }

    ...
}

MainViewController:

...
let vc = SecondViewController
vc.delegate = self
...

extension MainViewController: SecondViewControllerDelegate {
    func didSelectRow(at: YourLocationType) {
        // Direct user to annotation when cell of tableview tapped
    }
}
Kosuke Ogawa
  • 7,383
  • 3
  • 31
  • 52
  • Thank you for your answer! Could you have a look at the updated code? I have implemented the delegate pattern, but somehow it does not seem to work. – Jamie Coenen Apr 17 '19 at 17:59