kampbell411 kampbell411 - 6 months ago 25
Swift Question

Distance between locations from an Array of coordinates into a table

Im trying to populate a table with rows that show distances to locations from the users current location from an Array of coordinates. I have the coordinates in an empty array and I have the users location.

Im stuck on the getting the coordinates array into a function to calculate the distance, and then putting each distance value into a label in table rows. Any insight is greatly appreciated.

var locationArray: [CLLocationCoordinate2D] = []
var shopperLocation = String()
var distanceText: [String] = []

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

shopperLocation = userLocation

self.manager.stopUpdatingLocation()
}

//This is the function I am trying to use to get distance from the array of coordinates //

func distanceToLocation(){

if locationArray == nil {

locationArray = userLocation
}

let distanceBetween: CLLocationDistance = shopperLocation.distanceFromLocation(startLocation) / 1609.34

let distanceInMilesFromUser = String(format: "%.2f", distanceBetween)

distanceText = "\(distanceInMilesFromUser) mi"
}

// Here I am trying to get the distance values in the correct rows //

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! listViewCell

cell.myDistance.text = distanceText[indexPath.row]

return cell
}

J2b J2b
Answer

This is because you have to explicitly tell the table view to reload the data, that is to ask again its TableViewDataSource to configure every cell with fresh data.

You can easily do so by calling tableView.reloadData() in your CLLocationManager's delegate method. The code below is one way to achieve that. By the way, a few remarks:

  • I find it more elegant to initialise Arrays using var myArray: [Type]() than providing an explicit type and then assigning an empty array

  • You better save your shops parameters (name, coordinates, etc.) in a struct

  • Keep track of the current user location instead of the distance; doing so will enable you to easily add new shops without requesting the user location once again

  • Use MKDistanceFormatter to get a nice human-readable text output for your distances — this is very powerful and will adapt to your user's locale and preferred unit system for free!

Do not forget to import all required modules:

import UIKit
import CoreLocation
import MapKit

Define your constants:

let locationCellIdentifier = "LocationCell"

and your struct:

struct Shop {
    let name: String
    let coordinates: CLLocation
}

And then your view controller:

final class LocationTableView: UITableViewController, CLLocationManagerDelegate {

    var shops = [Shop]()
    var userLocation: CLLocation?
    let distanceFormatter = MKDistanceFormatter()

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        // Save the location and reloads table view
        if !locations.isEmpty {
            userLocation = locations.first
            manager.stopUpdatingLocation()
            tableView.reloadData()
        }
    }

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(locationCellIdentifier, forIndexPath: indexPath)

        // Configure the cell
        let shop = shops[indexPath.row]
        cell.textLabel?.text = shop.name

        if let userLocation = userLocation {
            let distance = shop.coordinates.distanceFromLocation(userLocation)
            let formattedDistance = distanceFormatter.stringFromDistance(distance)
            cell.detailTextLabel?.text = formattedDistance
        } else {
            cell.detailTextLabel?.text = nil
        }

        return cell
    }
}