I'm working on Day 78 of 100 days of SwiftUI and I've hit a point where I cannot solve the problem I'm having.
I set up a CLLocationManager and requestWhenInUseAuthorization (I have the proper entry in the info group for this) and that all works fine. I set the delegate for it to self.
Then I call requestLocation() on the CLLocationManager I set up. This fires off the didUpdateLocations delegate function.
I've got all kinds of print statements in that function that show that it's getting my current location and that I'm saving the results into a variable (lastKnownLocation). All of that code seems to be working fine and the variable gets set properly in that function.
The problem is, after that function returns, lastKnownLocation loses it's value and has a value of nil.
The problem shows up in my LocationFetcher class in the start() function after the call to requestLocation.
I've tried to solve this for a couple of days but I'm not coming up with anything.
I'm hoping some can review my code and spot some mistake I'm making.
There are comments and print statements peppered throughout the code.
Person struct and ViewModel class
import CoreLocation
import SwiftUI
struct Person: Identifiable, Codable, Comparable {
var id: UUID
let name: String
let imageData: Data
let latitude: Double
let longitude: Double
var coordinates: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
static func < (lhs: Person, rhs: Person) -> Bool {
lhs.name < rhs.name
}
}
extension Person {
var swiftUIImage: Image {
if let uiImage = UIImage(data: imageData) {
return Image(uiImage: uiImage)
} else {
return Image(systemName: "person.crop.square")
}
}
}
extension ContentView {
@MainActor class ViewModel: ObservableObject {
@Published var persons: [Person]
let savePath = FileManager.documentsDirectory.appendingPathComponent("SavedPersons")
let locationFetcher = LocationFetcher()
// create an instance of the LocationFetcher class
// which has an optional var called lastKnownLocation
// lastKnownLocation is set inside the locationManager didUpdateLocations
init() {
do {
// use Data() to allow us to use encryption when saving
let data = try Data(contentsOf: savePath)
let decodedData = try JSONDecoder().decode([Person].self, from: data)
persons = decodedData.sorted()
} catch {
persons = []
}
}
func getCurrentLocation() -> CLLocationCoordinate2D {
locationFetcher.start()
// lastKnownLocation is nil.
// somehow start is changing the value of lastKnownLocation
if let lastKnownLocation = locationFetcher.lastKnownLocation {
print("getCurrentLocation after start() - \(lastKnownLocation)")
} else {
print("getCurrentLocation after start() - lastKnownLocation is nil")
}
print("getCurrentLocation - lastKnownLocation: \(String(describing: locationFetcher.lastKnownLocation))")
// at this point lastKnownLocation is set to the default values
// even though it was written to properly in didUpdateLocations
if let location = locationFetcher.lastKnownLocation {
print("getCurrentLocation - Location: \(location)")
return location.coordinate
} else {
// because lastKnownLocation is nil return some default coordinates
print("getCurrentLocation - Failed to get last known location")
return CLLocationCoordinate2D(latitude: 50.0, longitude: 50.0)
}
}
func addPerson(name: String, inputImage: UIImage) {
// convert the UIImage to Data type
if let imageData = inputImage.jpegData(compressionQuality: 0.6) {
// entry point for CoreLocation code
let location = getCurrentLocation()
print("addPerson after getCurrentLocation - Location: \(location)")
let newPerson = Person(id: UUID(), name: name, imageData: imageData, latitude: location.latitude, longitude: location.longitude)
// append newPerson to persons array (in viewModel)
print("addPerson - newPerson: \(newPerson)")
// newPerson.latitude/longitude have default values
persons.append(newPerson)
save()
} else {
print("Failed to convert input image to data")
}
}
func save() {
do {
let data = try JSONEncoder().encode(persons)
// .completeFileProtection means that the file is stored with strong encryption
try data.write(to: savePath, options: [.atomic, .completeFileProtection])
} catch {
print("Unable to save data")
}
}
}
}
and here is the LocationFetcher class
import CoreLocation
class LocationFetcher: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
var lastKnownLocation: CLLocation?
override init() {
super.init()
manager.delegate = self
}
func start() {
manager.requestWhenInUseAuthorization()
print("In start() - called requestWhenInUseAuthorization using manager")
// manager.startUpdatingLocation()
manager.requestLocation()
print("In start() - called requestLocation using manager")
// after this it will call locationManager()
print("In start() after requestLocation - manager shows: \(manager.location!.coordinate)")
// this prints out valid coordinates
print("In start() - lastKnownLocation: \(String(describing: lastKnownLocation))")
// this prints that lastKnownLocation is nil
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// locations might be empty so it needs to be unwrapped
// the most recent location update is at the end of the array
if let location = locations.last {
print("Inside didUpdateLocations - location: \(location)")
// assign the unwrapped version of location
// store it in the optional lastKnownLocation
// locations.last?.coordinate has valid coordinates
lastKnownLocation = location
// now lastKnownLocation has valid coordinates
print("Inside didUpdateLocations - lastKnownLocation: \(lastKnownLocation as Any)")
// this accurately reflects that lastKnownLocation was set properly
} else {
print("Inside didUpdateLocations - Failed to get location coordinates")
lastKnownLocation = nil
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Inside didFailWithError - \(error)")
}
}