What I want is simple - if the geo-position has been determined, a certain method should be called that checks this geo-position.
here is my LocationManager:
final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
var lastKnownLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
@Published var locationState: LocationState = .loading
private var locationMngr: CLLocationManager!
func requestLocation() {
self.locationMngr = CLLocationManager()
self.locationMngr.delegate = self
switch self.locationMngr.authorizationStatus {
case .notDetermined:
self.locationMngr.requestWhenInUseAuthorization()
case .authorizedWhenInUse,
.authorizedAlways:
self.locationMngr.startUpdatingLocation()
default:
print("Location access denied or restricted")
}
}
[...]
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// this is always printed !
print("Location updated: \(locations)")
self.locationMngr.stopUpdatingLocation()
guard let location = locations.first else {
print("No location data available")
self.locationState = .notAvailable
return
}
self.lastKnownLocation = location.coordinate
self.locationState = .changed
}
}
enum LocationState: Equatable {
case loading
case changed
case notAvailable
static func == (lhs: LocationState, rhs: LocationState) -> Bool {
switch (lhs, rhs) {
case (.loading, .loading):
return true
case (.changed, .changed):
return true
case (.notAvailable, .notAvailable):
return true
default:
return false
}
}
}
Now the View:
struct RegistrationInfoView: View {
@StateObject private var locationManager = LocationManager()
var body: some View {
VStack {
// show some info text first
[...]
ButtonFilled("Next") {
locationManager.requestLocation()
}
}
.onChange(of: locationManager.locationState) {
print("onChange: Location changed")
if locationManager.locationState == .changed {
checkLocationIsInArea()
}
}
}
[...]
}
onChange
method is not triggered. I can see that didUpdateLocations
is triggered and position is available.
Perhaps XY-Problem or I'm using onChange
wrong. Any idea?
What I want is simple - if the geo-position has been determined, a certain method should be called that checks this geo-position.
here is my LocationManager:
final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
var lastKnownLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
@Published var locationState: LocationState = .loading
private var locationMngr: CLLocationManager!
func requestLocation() {
self.locationMngr = CLLocationManager()
self.locationMngr.delegate = self
switch self.locationMngr.authorizationStatus {
case .notDetermined:
self.locationMngr.requestWhenInUseAuthorization()
case .authorizedWhenInUse,
.authorizedAlways:
self.locationMngr.startUpdatingLocation()
default:
print("Location access denied or restricted")
}
}
[...]
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// this is always printed !
print("Location updated: \(locations)")
self.locationMngr.stopUpdatingLocation()
guard let location = locations.first else {
print("No location data available")
self.locationState = .notAvailable
return
}
self.lastKnownLocation = location.coordinate
self.locationState = .changed
}
}
enum LocationState: Equatable {
case loading
case changed
case notAvailable
static func == (lhs: LocationState, rhs: LocationState) -> Bool {
switch (lhs, rhs) {
case (.loading, .loading):
return true
case (.changed, .changed):
return true
case (.notAvailable, .notAvailable):
return true
default:
return false
}
}
}
Now the View:
struct RegistrationInfoView: View {
@StateObject private var locationManager = LocationManager()
var body: some View {
VStack {
// show some info text first
[...]
ButtonFilled("Next") {
locationManager.requestLocation()
}
}
.onChange(of: locationManager.locationState) {
print("onChange: Location changed")
if locationManager.locationState == .changed {
checkLocationIsInArea()
}
}
}
[...]
}
onChange
method is not triggered. I can see that didUpdateLocations
is triggered and position is available.
Perhaps XY-Problem or I'm using onChange
wrong. Any idea?
2 Answers
Reset to default 0Here is my test code that works for me, the
.onChange(of: locationManager.locationState)
is called as expected.
On macOS Sequoia 15.5, using Xcode 16.3, target iOS-18.4, tested on real iOS device.
Note you also need to have the appropriate Privacy - Location Usage Description
and Privacy - Location When in Use Usage Description
in your Info.plist
.
The first time you run the App, it asks for permition.
You then have to tap the Next
button again to start the location updating.
Let us know if this code does not work for you.
If it does not work with your own code, show a minimal reproducible code that produces your issue, see: minimal code.
struct ContentView: View {
var body: some View {
RegistrationInfoView()
}
}
struct RegistrationInfoView: View {
@StateObject private var locationManager = LocationManager()
var body: some View {
VStack {
Button("Next") {
locationManager.requestLocation()
}
}
.onChange(of: locationManager.locationState) {
print("---> onChange locationState: \(locationManager.locationState)")
if locationManager.locationState == .changed {
print("---> onChange location: \(locationManager.lastKnownLocation)")
}
}
}
}
final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
var lastKnownLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
@Published var locationState: LocationState = .loading
private var locationMngr = CLLocationManager()
override init() {
super.init()
self.locationMngr.delegate = self
}
func requestLocation() {
print("\nrequestLocation")
switch self.locationMngr.authorizationStatus {
case .notDetermined:
print("notDetermined")
self.locationMngr.requestWhenInUseAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
print("authorizedWhenInUse startUpdatingLocation")
self.locationMngr.startUpdatingLocation()
default:
print("Location access denied or restricted")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("---> Location updated: \(locations)")
self.locationMngr.stopUpdatingLocation()
guard let location = locations.first else {
print("------> No location data available")
self.locationState = .notAvailable
return
}
self.lastKnownLocation = location.coordinate
print("---> lastKnownLocation \(lastKnownLocation)\n")
self.locationState = .changed
}
}
enum LocationState {
case loading
case changed
case notAvailable
}
ok it seems to be an bug in swiftui. If I'm using onChange
on a Spacer()
it works:
struct RegistrationInfoView: View {
@StateObject private var locationManager = LocationManager()
var body: some View {
VStack {
// show some info text first
[...]
Spacer()
.onChange(of: locationManager.locationState) {
print("onChange: Location changed")
if locationManager.locationState == .changed {
checkLocationIsInArea()
}
}
ButtonFilled("Next") {
locationManager.requestLocation()
}
}
}
[...]
}
didUpdateLocations
is called on the main thread? – Sweeper Commented 9 hours agoPrivacy - Location Usage description
andPrivacy - Location When in Use Usage Description
in yourInfo.plist
. With those settings, your code works for me on real iOS 18 device. – workingdog support Ukraine Commented 7 hours ago