iPhone: No barometric pressure data in iOS 17.4

381 Views Asked by At

My app uses the barometric data from the CMAltimeter on iOS (iPhone). That has always worked just fine. But after installing iOS 17.4, it stopped working. Other apps that use barometric data also stopped working. So it appears to be a major fault in iOS 17.4.

Below is the (Swift) code used to test this. This code used to work fine with earlier versions of iOS, but in 17.4, the authorizationStatus() returns .restricted, and the handler is called with the error:

The operation couldn’t be completed. (CMErrorDomain error 105.)

This all indicates there is some authentication failure. I have logged bug report with Apple.

But I wonder if others experienced the same, and if there is some work-around/solution that makes barometric data available again.

if CMAltimeter.isRelativeAltitudeAvailable() {
    status = CMAltimeter.authorizationStatus()
    self.altimeter.startRelativeAltitudeUpdates(to: OperationQueue.main) { (data, _error) in
        DispatchQueue.main.async {
            error = _error != nil ? _error!.localizedDescription : "<nil>"
            print(error)
            pressure = data != nil ? String(format: "%f", data!.pressure.doubleValue) : ""
        }
    }
}

PS: the documentation of Core Motion (which CMAltimeter is part of) says that the NSMotionUsageDescription .info.plist property needs to be defined for all Core Motion service. I have that property in place, but it does not seem to make a difference.

3

There are 3 best solutions below

0
fishinear On BEST ANSWER

Note that the following is a work-around. I urge everybody to file a bug-report with Apple, to make sure there is pressure on Apple to fix this.

First of all, the NSMotionUsageDescription key must be defined in the app's .info.plist file.

Others have suggested the CMSensorRecorder work-around. It turns out that the "Fitness Tracking" setting in the Settings App needs to be enabled for that to work.

When the authorizationStatus is .restricted, then the user first needs to switch on Settings > "Privacy & Security" > "Motion & Fitness" > "Fitness Tracking"

When the authorizationStatus is .notDetermined, then start the CMSensorRecorder. A "Motion & Fitness" privacy settings will be added to your app's settings, and the user will be asked to authorise by iOS. When the user accepts, authorizationStatus changes to .authorized and you can start the CMAltimeter.

When the authorizationStatus is .denied, the user needs to switch on the "Motion & Fitness" privacy setting of your app.

The following code worked for me:

let status = CMAltimeter.authorizationStatus()
switch status {
case .notDetermined:
    // trigger a authorization popup
    let recorder = CMSensorRecorder()
    DispatchQueue.global().async {
       recorder.recordAccelerometer(forDuration: 0.1)
    }
case .restricted:
    popupText = "Please switch \"Fitness Tracking\" on, in the Apple Settings app, under \"Privacy & Security\" > \"Motion & Fitness\""
    showingPopup = true
case .denied:
    popupText = "Please switch \"Motion & Fitness\" on, in the app settings"
    showingPopup = true
default:
    print("authorized")
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
   let status = CMAltimeter.authorizationStatus()
   if status == .authorized {
      timer.invalidate()
      self.altimeter.startRelativeAltitudeUpdates(to: OperationQueue.main) { (data, _error) in
         // handle altimeter data
      }
   }
}
3
Chrystian On

You added the permission on the manifest, but you still need to trigger the motion and fitness permission. You can do it by recording sensor data for .1 sec:

let recorder = CMSensorRecorder() 
recorder.recordAccelerometer(forDuration: 0.1)
0
Ecuador On

CMSensorRecorder is not available on all devices and is also power hungry. It has been suggested that the pedometer is a better solution. I am making a new answer since the Objective-C version might be of use to some:

CMAltimeter *altimeter;

CMAuthorizationStatus status = [CMAltimeter authorizationStatus];
switch (status) {
    case CMAuthorizationStatusNotDetermined:
        {
            // Trigger an authorization popup
            [[[CMPedometer alloc] init] queryPedometerDataFromDate:[NSDate date] toDate:[NSDate date] withHandler:^(CMPedometerData * _Nullable data, NSError * _Nullable error) {
            }];
        }
        break;
    case CMAuthorizationStatusRestricted:
        // Popup here with @"Please switch \"Fitness Tracking\" on, in the Apple Settings app, under \"Privacy & Security\" > \"Motion & Fitness\" to be able to access the barometer data."
        break;
    case CMAuthorizationStatusDenied:
        // Popup here with @"Please switch \"Motion & Fitness\" permissions on, in the app settings to be able to access the barometer data.";
        break;
    default:
        NSLog(@"authorized");
        break;
}
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    CMAuthorizationStatus status = [CMAltimeter authorizationStatus];
    if (status == CMAuthorizationStatusAuthorized) {
        [timer invalidate];
        if (!self->altimeter)
            self->altimeter = [[CMAltimeter alloc] init];

        NSOperationQueue* queue = [[NSOperationQueue alloc] init];
        [self->altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData* altitudeData, NSError* error) {
            // handle altimeter data
        }];
    }
}];

Edit: Removed the popup code since it's specific to my app.