Home > Enterprise >  Display the distance between two coordinates in km in SwiftUI
Display the distance between two coordinates in km in SwiftUI

Time:09-17

In my project, I'm fetching the data about the houses/buildings and along with other details, I show how far it is from the user. I'm only a beginner, so I will try to be as much explicit as possible.

My issue is that I don't know where to put the function that calculates the distance in KM and how to call it properly in the MVVM project. See I have a ViewModel file that includes ViewModel class responsible for Networking and a LocationManager class responsible for tracking user location. The latitude and longitude come from the ViewModel from API and I believe the distance calculation should be made in LocationManager. I'm not sure how can I "connect" these two classes.

My main goals are:

  • Figure out where to put the func to calculate the distance. User coords are accessible from the LocationManager and house coords are accessible from the API. I would like to know if there's a way to merge these two classes to use the data in one func.

  • Understand if the distanceInKM method is correct. Despite it doesn't throw, it still displays a placeholder value.

As the minimum reproducible project, here's the code:

ContentView:

struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
@StateObject var locationManager = LocationManager()
var body: some View {
        VStack {
            HouseListView(housesVM: viewModel)   
        }
        .onAppear {
            viewModel.fetchHouses()
       }
}
}

HouseListView(A view that's called in ContentView:

struct HouseListView: View {
@ObservedObject var housesVM: ViewModel
@StateObject var locationManager = LocationManager()

var body: some View {
    ScrollView(.vertical, showsIndicators: false) {
        VStack(alignment: .leading) {
            ForEach(housesVM.info, id: \.id) { house in
                GetHouseCellView(
                    distance: Double(locationManager.distanceInKM(latitude: house.latitude, longitude: house.longitude)) ?? 0.00, //Here's the place where function is called
                    bedrooms: house.bedrooms)
            }
        }
    }
}
private func GetHouseCellView(distance: Double, bedrooms: Int) -> some View {
            
            HStack(spacing: 20) {
                Label("\(bedrooms)", image: "bed-2")
                Label("\(distance) km", image: "pin") //Here the final distance should be displayed, i.e 36,4 km
            }
        }

Quite basic ViewModel:

class ViewModel: ObservableObject {
    @Published var info: [Houses] = []
    func fetchHouses() {
        //URL & URLRequest here
            
            let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
                let decoder = JSONDecoder()
                do {
                    if let data = data {
                        let result = try decoder.decode([Houses].self, from: data) 
                        DispatchQueue.main.async {
                            self.info = result
                        }
                    }
                } catch {
                    print("whoopsie! There's an error: \(error.localizedDescription)")
                }
            }
            dataTask.resume()
        }
    }
    }

And a LocationManager:

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    private let locationManager = CLLocationManager()
    @Published var locationStatus: CLAuthorizationStatus?
    @Published var lastLocation: CLLocation?

    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }
    
    var statusString: String {
        guard let status = locationStatus else {
            return "unknown"
        }
        
        switch status {
        case .notDetermined: return "notDetermined"
        case .authorizedWhenInUse: return "authorizedWhenInUse"
        case .authorizedAlways: return "authorizedAlways"
        case .restricted: return "restricted"
        case .denied: return "denied"
        default: return "unknown"
        }
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        locationStatus = status
        print(#function, statusString)
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        lastLocation = location
        print(#function, location)
    }

    func distanceInKM(latitude: Int, longitude: Int) -> Double { //Here's the method I made to calculate an actual distance
        
        let houseCoordinates = CLLocation(latitude: CLLocationDegrees(latitude), longitude: CLLocationDegrees(longitude))
        
        let userCoordinates = CLLocation(latitude: lastLocation?.coordinate.latitude ?? 50, longitude: lastLocation?.coordinate.longitude ?? 30)
        let distance = userCoordinates.distance(from: houseCoordinates) / 1000 //.distance comes in meters so /1000 is to have a KM value
        
        let s = String(format: "%.0f", distance)
        
        return Double(s   "Km") ?? 35.5 // This value of 35.5 as placeholder is constantly displayed instead of the actual value
    }
}

This func logic was taken from this post

CodePudding user response:

Simple type error :

Double(s   "Km")

will always return nil as s "Km" is not a valid double.

Just Return :

Double(distance)

If you want to return a String change the type of the method and return

s   "km"

EDIT : If you want to display distance in km : in GetHouseCellView change distance label to :

Label(String(format: "%.2f km", distance), image: "pin")

It is better to format the distance Double when you use it.

  • Related