Search code examples
iosswiftuitableviewcllocationmanagermkmapitem

Swift Organize UITableView by Users Distance from MKMapItems


can someone please help me understand how to organize my tableview by distance that is already pulling and displaying the data on how far the mapItems are from the users location. Have not seen anyone give a answer on the actual tableview. Thank You.

import UIKit
import MapKit

class ListedMapTableViewController: UITableViewController, CLLocationManagerDelegate {

var mapItems: [MKMapItem]!
var userLocation = CLLocationManager()
let distanceFormatter = MKDistanceFormatter()


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

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "resultCell", for: indexPath) as! ListedTableViewCell

    // Configure the cell...
    let row = indexPath.row
    let item = mapItems[row]
    cell.nameLabel.text = item.name
    cell.detailLabel.text = item.phoneNumber
    let distanceInMeters : Double = self.userLocation.location!.distance(from: mapItems[row].placemark.location!)
    let distanceInMiles : Double = ((distanceInMeters.description as String).doubleValue * 0.00062137)
    cell.distanceLabel.text = "\(distanceInMiles.string(2)) miles away"


    return cell
}

}

//get string value of double without casting
extension String {
var doubleValue: Double {
    return (self as NSString).doubleValue
}
}

//formats a double's decimal places
extension Double {
func string(_ fractionDigits:Int) -> String {
    let formatter = NumberFormatter()
    formatter.minimumFractionDigits = fractionDigits
    formatter.maximumFractionDigits = fractionDigits
    return formatter.string(from: NSNumber(value: self))!
}
}

EDIT

import UIKit
import MapKit

class ListedMapTableViewController: UITableViewController, CLLocationManagerDelegate {

var mapItems: [MKMapItem]!
var userLocation = CLLocationManager()
let distanceFormatter = MKDistanceFormatter()

func sortedMapItems() -> [MKMapItem]! {
    return self.mapItems.sorted(by: { (a, b) -> Bool in
        return self.userLocation.location!.distance(from: a.placemark.location!) > self.userLocation.location!.distance(from: b.placemark.location!)
    })
}

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

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "resultCell", for: indexPath) as! ListedTableViewCell
    // Configure the cell...
    let row = indexPath.row
    let item = sortedMapItems()[row]
    cell.nameLabel.text = item.name
    cell.detailLabel.text = item.phoneNumber
    let distanceInMeters : Double = self.userLocation.location!.distance(from: sortedMapItems()[row].placemark.location!)
    let distanceInMiles : Double = ((distanceInMeters.description as String).doubleValue * 0.00062137)
    cell.distanceLabel.text = "\(distanceInMiles.string(2)) miles away"

    return cell
}

}

//get string value of double without casting
extension String {
var doubleValue: Double {
    return (self as NSString).doubleValue
}
}

//formats a double's decimal places
extension Double {
func string(_ fractionDigits:Int) -> String {
    let formatter = NumberFormatter()
    formatter.minimumFractionDigits = fractionDigits
    formatter.maximumFractionDigits = fractionDigits
    return formatter.string(from: NSNumber(value: self))!
}
}

SECOND EDIT

 import UIKit
 import MapKit

 class ListedMapTableViewController: UITableViewController, CLLocationManagerDelegate {

var mapItems: [MKMapItem]!
var userLocation = CLLocationManager()
let distanceFormatter = MKDistanceFormatter()

override func viewDidLoad() {
    super.viewDidLoad()

    func sortMapItems()  {
        self.mapItems = self.mapItems.sorted(by: { (a, b) -> Bool in
            return self.userLocation.location!.distance(from: a.placemark.location!) > self.userLocation.location!.distance(from: b.placemark.location!)
        })
    }
}

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

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

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "resultCell", for: indexPath) as! ListedTableViewCell
    // Configure the cell...
    let row = indexPath.row
    let item = mapItems[row]
    cell.nameLabel.text = item.name
    cell.detailLabel.text = item.phoneNumber
    let distanceInMeters : Double = self.userLocation.location!.distance(from: mapItems[row].placemark.location!)
    let distanceInMiles : Double = ((distanceInMeters.description as String).doubleValue * 0.00062137)
    cell.distanceLabel.text = "\(distanceInMiles.string(2)) miles away"

    return cell
}

}

//get string value of double without casting
extension String {
var doubleValue: Double {
    return (self as NSString).doubleValue
}
}

 //formats a double's decimal places
 extension Double {
 func string(_ fractionDigits:Int) -> String {
    let formatter = NumberFormatter()
    formatter.minimumFractionDigits = fractionDigits
    formatter.maximumFractionDigits = fractionDigits
    return formatter.string(from: NSNumber(value: self))!
}
}

Solution

  • Sort them by distance:

    func sortedMapItems() -> [MKMapItem] {
        return self.mapItems.sorted(by: { (a, b) -> Bool in
            return self.userLocation.location!.distance(from: a.placemark.location!) > 
                   self.userLocation.location!.distance(from: b.placemark.location!)
        })
    }
    

    EDIT: Create a function to sort your mapitems then call it in viewDidLoad:

    override func viewDidLoad() {
        super.viewDidLoad()
        self.sortMapItems()
    
    }
    func sortMapItems()  {
            self.mapItems = self.mapItems.sorted(by: { (a, b) -> Bool in
                return self.userLocation.location!.distance(from: a.placemark.location!) > self.userLocation.location!.distance(from: b.placemark.location!)
            })
        }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return mapItems.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "resultCell", for: indexPath) as! ListedTableViewCell
        // Configure the cell...
        let row = indexPath.row
        let item = mapItems[row]
        cell.nameLabel.text = item.name
        cell.detailLabel.text = item.phoneNumber
        let distanceInMeters : Double = self.userLocation.location!.distance(from: mapItems[row].placemark.location!)
        let distanceInMiles : Double = ((distanceInMeters.description as String).doubleValue * 0.00062137)
        cell.distanceLabel.text = "\(distanceInMiles.string(2)) miles away"
    
        return cell
    }
    

    Calling the original function (sortedMapItems) in this answer in cellForRowRowAtIndexPath will be too heavy and unnecessary because the function will be called repeatedly.

    A better approach would be re-create your data structure and add the distance of each items from user's location so that you won't have to call distance function again in your cellForRow..