Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
213 views
in Technique[技术] by (71.8m points)

swift - Calculate Distance between two Coordinates and display it inside an Label of an UITableViewCell

I am trying to calculate the distance between two Coordinates and showing them inside an Label of an UITableViewCell.

So far so good - my problem now is, that every time I scroll the tableview, the value of the label gets changed and the distances get completely mixed up... What I've read so far is, that this problem gets created due dequeuing and reusable data

But before I speak any further, this is my code:

class JobTableViewCell: UITableViewCell, CLLocationManagerDelegate {

@IBOutlet weak var distance: UILabel!

let location = CLLocationManager()
static var takenLocation: String?

override func layoutSubviews() {
    super.layoutSubviews()
    location.delegate = self
    self.location.startUpdatingLocation()
}

    
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    if let lastLocation = locations.last {
        let geocoder = CLGeocoder()
        //get job coordinates
        geocoder.geocodeAddressString(job.location) { placemarks, error in
            let placemarkW = placemarks?.first
            if let placemark = placemarkW
            {
                let lat = placemark.location?.coordinate.latitude
                let lon = placemark.location?.coordinate.longitude
                let jobLocation = CLLocation(latitude: lat!, longitude: lon!)
                //get user coordinates
                let myLocation = CLLocation(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)
                //get distance between coordinates
                let distance = myLocation.distance(from: jobLocation) / 1000
                self.distance.text = String(format: "%.01fkm", distance)
                self.job.distance = distance
                //JobTableViewCell.takenLocation = String(format: "%.01km", distance)
            } else {
                self.distance.text = "Not Valid"
                self.job.distance = 0.0

            }
            self.reloading?.reloadIt()
        }
    }
    self.location.stopUpdatingLocation()
    guard let _: CLLocationCoordinate2D = manager.location?.coordinate else { return }
}

}

I don't have any trouble calculating or showing the distance, my only problem is that I don't know how to reuse the LabelData(if this is even the correct approach :/ )

As far as I know, I need to go over to the TableViewController and write something like cell.distance.text = idonwknowwhat, but that's the point where I stuck

UPDATE:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
    let cell = tableView.dequeueReusableCell(withIdentifier: "JobCell", for: indexPath) as! JobTableViewCell
    let job = jobs[indexPath.row]
    cell.job = job
    cell.jobHeader.text = job.postHeader //just leave this line so the function is not empty 


    //cell.cellDelegate = self
    return cell
}
question from:https://stackoverflow.com/questions/65831084/calculate-distance-between-two-coordinates-and-display-it-inside-an-label-of-an

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are many issues here.

  1. It's not terribly important, but the process of getting a placemark, finding the location, getting latitude and longitude, and building a new location is unnecessarily convoluted. The CLPlaceMark array returned from geocodeAddressString has a location which is the CLLocation, so just use that directly:

    geocoder.geocodeAddressString(job.location) { placemarks, error in
        guard
            let jobLocation = placemarks?.first(where: { $0.location != nil })?.location
        else {
            // handle error where no placemark with a valid location was found
            return
        }
    
        // get distance between coordinates
    
        let distance = myLocation.distance(from: jobLocation) / 1000
    
        // now update model or UI here
    }
    

    Likewise, the didUpdateLocations is giving you an array of CLLocation objects, so getting the last valid location (i.e. where horizontalAccuracy is non-negative) is one line of code:

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard
            let myLocation = locations.last(where: { $0.horizontalAccuracy >= 0 }) 
        else { return }
    
        // now just use `myLocation`
    }
    
  2. The geocoding of the job.location does not belong in the didUpdateLocations.

    If your current location updates, do you really need to geocode the job’s location again? (I know you appear to be canceling the location updating, but it suggests a general conflation of the logic of the user’s location and the geocoding process.)

    Furthermore, I would suggest that the Job object should not have a distance property, but rather just a coordinate property that is the CLLocationCoordinate2D. The coordinates are a property of the job, not the distance from where you happen to be at that moment. The CLLocationDistance of the job to the user’s current location should be calculated as cells are displayed and updated.

    The virtue of decoupling the location updates from the geocoding is that you can easily, for example, keep the location updating if you want. Obviously, if you are only showing distances to the nearest tenth of a kilometer, you might add a distanceFilter of 100 meters, to reduce the amount of unnecessary work being performed.

  3. You have made the CLLocationManager a property of the cell (given that your delegate method is updating cell properties). The manager should not be a property of the cell. You really only need/want a single CLLocationManager for the entire view (or possibly the entire app).

  4. You are updating cell properties inside an asynchronous completion handler closure. You need to be very careful, because you do not know if the cell has been reused by the time the closure finishes. If you do asynchronously want to update a cell, you should requery the model collection to get the updated row.

So, pulling this all together, I would suggest:

  • Change the Job model to capture the coordinates, not the distance;

  • When cell is to be shown, geocode the address if needed (saving the coordinates in the model to save needing to perform redundant geocoding requests);

  • Have a single CLLocationManager instance, just reloading the table when the location changes.

See https://github.com/robertmryan/GeocoderTableView.git for example.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...