First, I am a very novice coder learning as I make my app, so the app is probably comprised of unnecessary and unconventional code. Apologies in advance. Because of my lack of coding knowledge I would like to fix this while making as few changes to the code as possible as to not require me to rewrite large chunks of the app.
I am using CLLocation to find the distance between two points in a golf app to track shot distances. When I print the locations to console, the coordinates point to the correct locations, but when I find the distance between the two points the value returned is huge even though both coordinates are actually very close together.
In this DetailView, pressing the "Swing Location" button grabs the user location and sets that as shotCoord, then the button becomes "Ball Location." When "Ball Location" is pressed its grabs the location again and sets it to ballCoord, then prints the distance between ballCoord and shotCoord. I am coding on Swift Playgrounds for iPad and using location services so the location grabbed is my actual device location.
struct ClubDetailView: View {
@ObservedObject var club: Club
@Environment(\.managedObjectContext) private var viewContext
@StateObject var locationManager = LocationManagerModel()
@State var newShot: Int = 0
@Binding var waiting: Bool
@Binding var shotCoord: CLLocation
@Binding var ballCoord: CLLocation
var body: some View{
List {
Section {
Text(self.club.name)
Text("Average distance: \(club.yardsNum) yards")
Text("\(club.strokes) Strokes Counted")
}
Section{
TextField("", value: $newShot, format: .number)
.onSubmit {
do {
addNewShot(newShot: newShot)
try viewContext.save()
} catch {
print("error")
}
}
}
}
if self.waiting == false{
Button(action: {
let shotCoords = getShotLocation()
print("shotCoords = \(shotCoords)")
self.waiting = true
}, label: {
Text("Swing Location")
.foregroundColor(.white)
.font(.system(.title, design: .rounded, weight: .bold))
.frame(maxWidth: .infinity)
})
.buttonStyle(.borderedProminent)
}
if self.waiting == true{
Button(action: {
let ballCoords = getBallLocation()
//print("ballCoord = \(ballCoords)")
let distance = ballCoords.distance(from: shotCoord)
print(distance)
self.waiting = false
}, label: {
Text("Ball Location")
.foregroundColor(.white)
.font(.system(.title, design: .rounded, weight: .bold))
.frame(maxWidth: .infinity)
})
.buttonStyle(.borderedProminent)
}
}
private func addNewShot(newShot: Int) -> Void {
let newShot = newShot
let avgYards = club.yardsNum * club.strokes
club.strokes += 1
club.yardsNum = (avgYards + newShot) / club.strokes
}
func getShotLocation() -> CLLocation {
locationManager.requestAllowOnceLocationPermission()
let shotCoord = locationManager.lastLocation
return shotCoord!
//print(shotCoords as Any)
}
func getBallLocation() -> CLLocation {
locationManager.requestAllowOnceLocationPermission()
let ballCoords = locationManager.lastLocation
//let distance = ballCoords?.distance(from: shotCoord)
return ballCoords!
//print(ballCoords as Any)
}
}
This code results in shotCoords and ballCoords being printed to the console with my actual current location, but distance returns "9582674.806193907". Since I'm sitting in one place while grabbing both locations, and the coordinates in console are the same down to the last 4 digits, distance should actually be close to 0.
Here is my LocationManager
final class LocationManagerModel: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published var locationStatus: CLAuthorizationStatus?
@Published var lastLocation: CLLocation?
let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func requestAllowOnceLocationPermission() {
locationManager.requestLocation()
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let latestLocation = locations.first else {
print("error")
return
}
DispatchQueue.main.async {
self.lastLocation = latestLocation
}
//self.location = lastLocation?.coordinate
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
There is a basic mistake. Your
LocationManagerModelcannot work because receiving locations works asynchronously. ReadinglastLocationright after callingrequestAllowOnceLocationPermissionwill never show the requested location, because it takes some time untildidUpdateLocationsis called and the location is updated.I recommend to declare
shotCoordandballCoordinsideLocationManagerModelas@Publishedto get notified when the location is available. You can distinguishballandshotwith an enum. The code handles also the authorization. Don't forget to set theNSLocationWhenInUseUsageDescriptionkey inInfo.plist.If you want to call
requestLocation()to get a location once do not callstartUpdatingLocation()andstopUpdatingLocation()simultaneously.With the second pair of
@Publishedproperties you can show an error in the viewThe view is very simple, it contains only the two buttons to get the locations
The
printlines inlocationManagerDidChangeAuthorizationare only for debugging purposes. Actually you need onlynotDeterminedto callrequestWhenInUseAuthorization()anddenied/restrictedto show an error