0

I have been attempting to display the distance on a tableView but I am unable to get it to happen. This question follows up from this question: CLLocationDistance conversion. I have checked the distance. Using this function in my Location class:

// Get distance
func distance(to location: CLLocation) -> CLLocationDistance {
    return location.distance(from: self.location)
}

How I get the users current location:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let location = locations[0]

    let span:MKCoordinateSpan = MKCoordinateSpanMake(0.01, 0.01)
    let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
    let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
    mapView.setRegion(region, animated: true)

    // Add a lastUserLocation to LocationManager and update it every time that the delegate receives a new location
    LocationManager.shared.lastUserLocation = locations.last
    LocationManager.shared.sortLocationsInPlace()

    self.mapView.showsUserLocation = true

}

Sort function in LocationManager:

func getSortedLocations(userLocation: CLLocation) -> [Location] {
        return locations.sorted { (l1, l2) -> Bool in
            return l1.distance(to: userLocation) < l2.distance(to: userLocation)
        }
    }

func sortLocationsInPlace() {
    if let validLocation = lastUserLocation {
        locations.sort { (l1, l2) -> Bool in
            return l1.distance(to: validLocation) < l2.distance(to: validLocation)
        } 
    }
}

cellForRowAt:

var sortedLocations = [Location]()

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath)


    let location = sortedLocations[indexPath.row]

    cell.textLabel?.text = location.name
    return cell
}

Update

Inside Location class:

class Location {

    var name: String
    var latitude: Double
    var longitude: Double
    var location:CLLocation {
        return CLLocation(latitude: latitude, longitude: longitude)
    }

    init?(json: JSON) {

        guard let name = json["name"] as? String, let latitude = json["latitude"] as? Double, let longitude = json["longitude"] as? Double else { return nil }
        self.name = name
        self.latitude = latitude
        self.longitude = longitude
    }

    func distance(to location: CLLocation) -> CLLocationDistance {
        return location.distance(from: self.location)
    }
}
Chace
  • 561
  • 10
  • 28
  • Your question does not provide enough information. What are the entries in the array that you display in a table view? CLLocation locations of different objects? Are you supposed to be calculating the distance between an array of locations and the user's current location? How is your function `distance()` being called? Please edit your question to provide information about the above. – Duncan C Oct 16 '17 at 01:12
  • what do you have in `LocationManager.shared.location`? it seems to me that you are using it instead of `lastUserLocation` to do your comparisons – jvrmed Oct 16 '17 at 10:32
  • @JavierMedina Please see my updated question – Chace Oct 16 '17 at 11:10

1 Answers1

2

Considering your code, I am making some assumptions:

  1. Your sortedLocations array has different locations that you extracted from a JSON or whatever.
  2. You call startUpdatingLocation() or similar somewhere before loading your data.
  3. You are receiving updates in your didUpdateLocations.
  4. Your LocationManager keeps an ordered copy of all your locations in a variable called locations, the one you are ordering inside didUpdateLocations.

That considered, what I understand you want to do is to display your sortedLocations ordered according to a reference location.

What is missing is to update your UITableView data once your user location is received. You have two main options:

  1. To only load your UITableView once you have already your first user location retrieved by didUpdateLocations.
  2. To force a UITableView update once you get a new location, by calling tableView.reloadData() inside didUpdateLocations. This will redraw your list every time you receive a location update, sorting them by location.

However, in any of those cases you need to replace your cellForRow text to display your distance instead of location.name:

// Distance in meters
cell.textLabel?.text = String(location.distance(to: LocationManager.shared.lastUserLocation!))

// Distance in miles
cell.textLabel?.text = String(location.distance(to: LocationManager.shared.lastUserLocation!)*0.00062137)

And update your didUpdateLocations:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let location = locations.last {

        let span:MKCoordinateSpan = MKCoordinateSpanMake(0.01, 0.01)
        let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
        let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
        mapView.setRegion(region, animated: true)

        // Add a lastUserLocation to LocationManager and update it every time that the delegate receives a new location
        LocationManager.shared.lastUserLocation = location
        LocationManager.shared.sortLocationsInPlace()

        sortedLocations = LocationManager.shared.locations
        tableView.reloadData()

        self.mapView.showsUserLocation = true
    }
}

With your current code you are comparing all distances with a self.location variable that its not being initialised anywhere apparently.

jvrmed
  • 834
  • 6
  • 12
  • I am aiming to force the the UITableView to update once you get a new location so it is constantly updating. – Chace Oct 16 '17 at 12:53
  • then calling reloadData and fixing your distance functions should be enough – jvrmed Oct 16 '17 at 12:58
  • I have tried that and also defined it as `var lastUserLocation: CLLocation?` but `self.lastUserLocation` is nil – Chace Oct 16 '17 at 12:59
  • if you put a breakpoint inside `didUpdateLocations`, is it called at any time? – jvrmed Oct 16 '17 at 13:00
  • It does, but when I print out: `LocationManager.shared.lastUserLocation = locations.last` I get nil – Chace Oct 16 '17 at 13:02
  • I did that before where you made a func with `sortedLocations = LocationManager.shared.locations` and `tableView.reloadData()` and called it at the bottom of locationManager, but still getting a nil – Chace Oct 16 '17 at 13:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/156835/discussion-between-jvrmed-and-mninety5). – jvrmed Oct 16 '17 at 13:07
  • @mninety5 please remember to accept the answer if it was helpful ;) – jvrmed Oct 16 '17 at 14:43
  • I know this is a bit delayed but how would I achieve the other option which you suggested which is _"To only load your UITableView once you have already your first user location retrieved by didUpdateLocations"_. I'd like it to be sorted once they press a button, and only sort once. I currently can perform the `sortedLocations = LocationManager.shared.locations` and `reloadData()` at the button action but then the tableView is empty until this action performs. – Chace Oct 17 '17 at 09:10
  • either by using a delegate or subscribing your table to a notification, user NotificationCenter – jvrmed Oct 17 '17 at 10:30