简体   繁体   中英

UI Tableview won't display or update search results in cells, Swift

I am an intermediate Swift developer and I am creating an app that involves using a search function to find an address and pinpoint said address on a map. I followed a tutorial on how to achieve this and everything is functional besides the search function itself. Whenever I type in the UIsearchbar my search results tableview controller is instantiated, however, the table view is blank and does not update as I type. An address API call should be present.

Below is my code for the search table

import UIKit
import MapKit


class LocationSearchTable : UITableViewController {
    
    var resultSearchController:UISearchController? = nil
    var handleMapSearchDelegate:HandleMapSearch? = nil
    var matchingItems:[MKMapItem] = []
    var mapView: MKMapView? = nil
    @IBOutlet var searchTableView: UITableView!
    
}
extension LocationSearchTable : UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        searchController.showsSearchResultsController = true
    }
    
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        guard let mapView = mapView,
              let searchBarText = searchController.searchBar.text else { return }
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = searchBarText
        request.region = mapView.region
        let search = MKLocalSearch(request: request)
        search.start { response, _ in
            guard let response = response
            else {
                return
            }
            self.matchingItems = response.mapItems
            self.tableView.reloadData()
        }
    }

        
    
}
extension LocationSearchTable {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        return matchingItems.count
        
    }
    func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
        let selectedItem = matchingItems[indexPath.row].placemark
        cell.textLabel?.text = selectedItem.name
        cell.detailTextLabel?.text = ""
        return cell
    }
}
extension LocationSearchTable {
        func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let selectedItem = matchingItems[indexPath.row].placemark
            handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
            dismiss(animated: true, completion: nil)
    }
}

This is my code for my initial view controller

import UIKit
import MapKit
import CoreLocation

protocol HandleMapSearch {
    func dropPinZoomIn(placemark:MKPlacemark)
}
//for some reason my location delegate is not working (resolved)
class locationViewController: UIViewController, UISearchResultsUpdating, UISearchBarDelegate {
    
    func updateSearchResults(for searchController: UISearchController) {
        return
        
        
    }
    
   
    
    var selectedPin:MKPlacemark? = nil
    
    var resultSearchController:UISearchController? = nil
    @IBOutlet weak var eventLocationMapView: MKMapView!
    @IBOutlet weak var backButton: UIButton!
    
    let locationManager = CLLocationManager()
    let regionInMeters: Double = 10000
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
       
        resultSearchController?.searchResultsUpdater = self
        
        let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
        resultSearchController = UISearchController(searchResultsController: locationSearchTable)
        resultSearchController?.searchResultsUpdater = locationSearchTable
        resultSearchController?.searchBar.delegate = self
       
            locationSearchTable.mapView = eventLocationMapView
       
        let searchBar = resultSearchController!.searchBar
                
        searchBar.sizeToFit()
                
        searchBar.placeholder = "Search for places"
        //this is the search bar
        navigationItem.titleView = resultSearchController?.searchBar
                
        resultSearchController?.hidesNavigationBarDuringPresentation = false
                
        resultSearchController?.obscuresBackgroundDuringPresentation = true
                
        definesPresentationContext = true
        
        locationSearchTable.handleMapSearchDelegate = self
        //all of this is in regards to the search functionality
        
        locationManager.startUpdatingLocation()
        self.checkLocationAuthorization()
        self.checkLocationServices()
        self.navigationController?.isNavigationBarHidden = false
        // Do any additional setup after loading the view.
        locationManager.requestWhenInUseAuthorization()
        //this pushes the user request. The issue was most likely in the switch statement
        locationManager.delegate = self
        locationManager.requestLocation()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest

        //solved it but we will probably have to go back to this
        
        self.centerViewOnUserLocation()
    }
        
    @IBAction func backButtonTapped(_ sender: Any) {
        
        self.transitionBackToCreateEventVC()
        
    }
        

    func transitionBackToCreateEventVC (){
        let createEventViewController = self.storyboard?.instantiateViewController(identifier: "createEventVC")
        
        self.view.window?.rootViewController = createEventViewController
        self.view.window?.makeKeyAndVisible()
        
        
        
    }
    func centerViewOnUserLocation() {
        
        if let location = locationManager.location?.coordinate {
            let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
            eventLocationMapView.setRegion(region, animated: true)
            //this function centers the map onto the location of the user
        }
        
    }
    
    
    func setUpLocationManager(){
        
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        
    }
    func checkLocationServices(){
        
        if CLLocationManager.locationServicesEnabled(){
            setUpLocationManager()
            checkLocationAuthorization()
        } else {
            //show alert letting the user know they have to turn this on
        }
        
    }
    func checkLocationAuthorization() {
        
        let locationManager = CLLocationManager()
        switch locationManager.authorizationStatus {
        case .authorizedWhenInUse:
            
            eventLocationMapView.showsUserLocation = true
            // We want the users location while the app is in use
            break
        case .denied:
            //show alert instructing how to turn on permissions
            break
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
            return
        case .restricted:
            //show an alert
            break
        case .authorizedAlways:
            // we don't want this
            break
        @unknown default:
            return
            //I dont know if we need this code
        }
        
    }
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}
extension locationViewController: CLLocationManagerDelegate {
    
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if status == .authorizedWhenInUse {
              locationManager.requestLocation()
          }
      }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
       
        if let location = locations.first {
            
            let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 0.05 , longitudinalMeters: 0.05)
                    eventLocationMapView.setRegion(region, animated: true)
            
        }
        
    }
    public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print("error:: \(error)")
    }
}
extension locationViewController: HandleMapSearch {
    func dropPinZoomIn(placemark:MKPlacemark){
        // cache the pin
        selectedPin = placemark
        // clear existing pins
        eventLocationMapView.removeAnnotations(eventLocationMapView.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)"
        }
        eventLocationMapView.addAnnotation(annotation)
        let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
        let region = MKCoordinateRegion(center: placemark.coordinate, span: span)
        eventLocationMapView.setRegion(region, animated: true)
    }
}


I think the you should make a property to hold the MKLocalSearch object in your LocationSearchTable class.

class LocationSearchTable : UITableViewController {
    private var search: MKLocalSearch?
    // ...
}

extension LocationSearchTable : UISearchResultsUpdating {
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        guard let mapView = mapView,
              let searchBarText = searchController.searchBar.text else { return }
        // if there is an ongoing search, cancel it first.
        self.search?.cancel()

        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = searchBarText
        request.region = mapView.region
        let search = MKLocalSearch(request: request)
        search.start { response, error in
            guard let response = response
            else {
                return
            }
            self.matchingItems = response.mapItems
            self.tableView.reloadData()
        }
        // make sure `search` is not released.
        self.search = search
    }

}

The problem of the original code is that, the search object, instance of MKLocalSearch class will be released when finishes executing updateSearchResultsForSearchController method, since there is no strong reference to it, and start 's callback will never be called, so your table view will never be reloaded.

What we do is just make a strong ref to it, and make sure it is not released before completion handler is called.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM