1

I am able to display the list of locations on a table. But now I want to sort it based on the current location.

This is how I display my locations:

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

    let location = LocationManager.shared.locations[indexPath.row]
    cell.textLabel?.text = location.name
    return cell
}

I have tried to implement this distance in my Location class which I got from https://stackoverflow.com/a/35200027/6362735 but I'm not sure what I need to do next and how to sort it.

Here is my 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)
    }
}

Displaying the 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)

    self.mapView.showsUserLocation = true
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Chace
  • 561
  • 10
  • 28

2 Answers2

2

What you need to do now is to sort LocationManager.shared.locations by distance and passing your user's location as parameter. You can put these two inside your LocationManager.

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

func sortLocationsInPlace(userLocation: CLLocation) {
    locations.sort { (l1, l2) -> Bool in
        return l1.distance(to: userLocation) < l2.distance(to: userLocation)
    }
}

After you sort the locations, call tableView.reloadData() and your rows should be ordered by distance.

Where to use this code depends on how your app is structured.

If you use a button to filter, you can sort your locations inside the action:

@IBAction func orderByDistance() {
    sortLocationsInPlace(userLocation: yourUsersLocation)
    tableView.reloadData()
}

If you want your data to be always ordered, you can sort it when you create your tableView for the first time. Inside your UIViewController:

let sortedLocations: [Location]()

override func viewDidLoad() {
    sortedLocations = getSortedLocations(userLocation: yourUsersLocation)
}

and then you can change your dataSource method for:

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
}

Also, remember that you can use either sort or sorted depending if you want to sort in place or create a sorted copy. For more information about how the CLLocationManager works you should read here.

jvrmed
  • 834
  • 6
  • 12
  • Thanks for your response. But could you elaborate where I'd call this on my app? – Chace Oct 13 '17 at 09:14
  • It depends on how your app is structured. If you have a button or something that triggers the filtering action, you should put it there. If your want your locations to be always sorted by distance, you can put it inside your TableView's controller in `viewDidLoad` for example. I will update the answer with both cases. – jvrmed Oct 13 '17 at 09:26
  • What do I put in the parameters though? When I use your sort function it just says: `Missing argument for parameter 'to' in call`. It's expecting a `CLLocation`. – Chace Oct 13 '17 at 09:36
  • updated again, I missed the fact that your function `distance` has a `to:` parameter, sorry! – jvrmed Oct 13 '17 at 09:40
  • Now I get this error `Cannot convert value of type 'Location' to expected argument type 'CLLocation'`. – Chace Oct 13 '17 at 09:42
  • I updated the answer so you can see both *sort in place* and *copy sorted* side to side and added what was missing: sorting all locations comparing to user location. What you have to do now is to retrieve your userLocation first and then call one of those methods to obtain your locations sorted according to the same location. – jvrmed Oct 13 '17 at 09:52
  • Thank you for this. But this has been my struggle. Getting the users location so that i can call it in other places. If you look at my code. You'll see how I get the users current location. Do you know how I can get that to the parameter? If I do `LocationManager.shared.sortLocationsInPlace(userLocation: mapView.userLocation.location!)` this works. but is that correct? – Chace Oct 13 '17 at 09:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/156640/discussion-between-javier-medina-and-mninety5). – jvrmed Oct 13 '17 at 09:57
  • Could you please see this question as I believe it relates to our last conversation: https://stackoverflow.com/questions/46761442/display-the-distance-from-user-with-mapkit-swift-miles-km?noredirect=1#comment80468621_46761442 – Chace Oct 16 '17 at 10:10
1

Use your distance function to sort the resulting array by left < right:

locations.sorted(by: { $0.distance(to: myLocation) < $1.distance(to: myLocation) } )

Here's a working example you can test with:

import Foundation
import CoreLocation

struct Location {

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

    init(lat: Double, long: Double) {
        self.latitude = lat
        self.longitude = long
    }

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

let locations: [Location] = [
    Location(lat: 61.98573, long: 27.57300),
    Location(lat: -62.98404, long: 62.81190),
    Location(lat: -3.18446, long: 107.07900)]

let myLocation = CLLocation(latitude: 73.30051, longitude: -141.88647)

let locationsClosestToMe = locations.sorted(by: { $0.distance(to: myLocation) < $1.distance(to: myLocation) } )

Proving the function (unit test):

print(locationsClosestToMe.map { $0.distance(to: myLocation) } )

/*
[
  4970593.6601553941, 
  11003159.607318919, 
  18486409.053517241
]
*/
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • Thank you for your response. I am a little a bit confused how I can use `myLocation` to be the current location rather than a hard-coded location? My code shows how I get the current location. But it is a `CLLocationCoordinate2DMake` and not a `CLLocation` like your method requires. Also how do I get the data it to display on my table? – Chace Oct 13 '17 at 09:13
  • 1
    Have a look at https://stackoverflow.com/a/25698536/1214800? it should do what you need. Either way, you initialize the 2DMake with lat/long values the same as a standard `CLLocation` object is initialized. – brandonscript Oct 13 '17 at 14:38