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.