I am using the following code from the Privacy Prompts project to get the Motion Permission.
- (void)requestMotionAccessData {
self.cmManager = [[CMMotionActivityManager alloc] init];
self.motionActivityQueue = [[NSOperationQueue alloc] init];
[self.cmManager startActivityUpdatesToQueue:self.motionActivityQueue withHandler:^(CMMotionActivity *activity) {
/*
* Do something with the activity reported
*/
NSLog(#"requestMotionAccessData");
[self alertViewWithDataClass:Motion status:NSLocalizedString(#"ALLOWED", #"")];
[self.cmManager stopActivityUpdates];
}];
}
What if user not allow the motion permission. Do i get some callback?
If not is there an alternative way to get the this. I want the callback when user selects Allow or Don't Allow
you just can ... picking the error:
[stepCounter queryStepCountStartingFrom:[NSDate date]
to:[NSDate date]
toQueue:[NSOperationQueue mainQueue]
withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (error != nil && error.code == CMErrorMotionActivityNotAuthorized) {
// The app isn't authorized to use motion activity support.
}
from here: iOS - is Motion Activity Enabled in Settings > Privacy > Motion Activity
Related
I want to check if the Motion permission is enabled or not, but the only way I have found is this (support pre iOS 11 also):
CMMotionActivityManager * activityManager = [[CMMotionActivityManager alloc]init];
NSDate* today = [NSDate date];
[activityManager queryActivityStartingFromDate:today toDate:today toQueue:[NSOperationQueue mainQueue] withHandler:^(NSArray<CMMotionActivity *> * _Nullable activities, NSError * _Nullable error) {
NSLog(#"result is %d",error && error.code != CMErrorMotionActivityNotAvailable);
}];
}
But I don't want to ask automatically for the permission, only check its status.
Is it possible?
You can use authorizationStatus class property.
if (CMMotionActivityManager.authorizationStatus == CMErrorMotionActivityNotAvailable)
This should not fire permission request.
To check status only, use the class function authorizationStatus.
CMMotionActivityAuthorizationStatus status = [CMMotionActivityManager authorizationStatus]
This returns a value from the CMAuthorizationStatus enum:
notDetermined
restricted
denied
authorized
notDetermined means your app hasn’t asked for permission yet.
Use the function isActivityAvailable to determine “whether motion data is available on the device“
I'm working on an iOS application in which I use Motion Activity Manager (in more detail - pedometer). When application launches I need to check is Motion Activity is allowed by user. I do this by doing
_motionActivityManager = [[CMMotionActivityManager alloc] init];
_pedometer = [[CMPedometer alloc] init];
[_pedometer queryPedometerDataFromDate : [NSDate date]
toDate : [NSDate date]
withHandler : ^(CMPedometerData *pedometerData, NSError *error) {
// BP1
if (error != nil) {
// BP2
}
else {
// BP3
}
}];
As discussed here ☛ iOS - is Motion Activity Enabled in Settings > Privacy > Motion Activity
In my understanding this code will trigger "alert window" asking user to opt-in/out.
What happens in my case is that when I run application first time (aka. all warnings are reset), application hangs before 'BP1' (callback is never executed) and then if I stop application with xCode or press home button "alert window" appears. And if I opt-in everything is good, on second run 'BP3' is reached (or 'BP2' if I opt-out).
What I tried do far:
I implemented another way of checking using async execution
[_pedometer queryPedometerDataFromDate : [NSDate date]
toDate : [NSDate date]
withHandler : ^(CMPedometerData *pedometerData, NSError *error) {
// Because CMPedometer dispatches to an arbitrary queue, it's very important
// to dispatch any handler block that modifies the UI back to the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
authorizationCheckCompletedHandler(!error || error.code != CMErrorMotionActivityNotAuthorized);
});
}];
This doesn't hang the application, but "alert window" is never showed
I executed this "checking snippet" in later time in code - but again - application hangs
Essentially, use can first be sure that the alert view will not block your App, when the first view has appeared, ie. in onViewDidAppear.
For example do:
-(void) viewDidAppear:(BOOL)animated {
if ([MyActivityManager checkAvailability]) { // motion and activity availability checks
[myDataManager checkAuthorization:^(BOOL authorized) { // is authorized
dispatch_async(dispatch_get_main_queue(), ^{
if (authorized) {
// do your UI update etc...
}
else {
// maybe tell the user that this App requires motion and tell him about activating it in settings...
}
});
}];
}
}
This is what I do myself. I based my App on the Apple example code as well and noticed, that the example also has the problem you are describing.
WWDC 2014 Session 612 (45:14) highlights how to check the authorization status of Core Motion Services:
- (void)checkAuthorization:(void (^)(BOOL authorized))authorizationCheckCompletedHandler {
NSDate *now = [NSDate date];
[_pedometer queryPedometerDataFromDate:now toDate:now withHandler:^(CMPedometerData *pedometerData, NSError *error) {
// Because CMPedometer dispatches to an arbitrary queue, it's very important
// to dispatch any handler block that modifies the UI back to the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
authorizationCheckCompletedHandler(!error || error.code != CMErrorMotionActivityNotAuthorized);
});
}];
}
While this works, the first call to -queryPedometerDataFromDate:toDate:withHandler: will prompt the user for authorization via a system dialog. I would prefer to check the status without having to ask the user for permission for obvious UX reasons.
Is what I am trying to achieve possible or am I just thinking about the API the wrong way?
For iOS 11: Use the CMPedometer.authorizationStatus() method. By calling this method, you can determine if you are authorized, denied, restricted or notDetermined.
https://developer.apple.com/documentation/coremotion/cmpedometer/2913743-authorizationstatus
For devices running iOS 9-10, use CMSensorRecorder.isAuthorizedForRecording().
Here's a method that will work for all devices running iOS 9-11:
var isCoreMotionAuthorized: Bool {
if #available(iOS 11.0, *) {
return CMPedometer.authorizationStatus() == .authorized
} else {
// Fallback on earlier versions
return CMSensorRecorder.isAuthorizedForRecording()
}
}
[stepCounter queryStepCountStartingFrom:[NSDate date]
to:[NSDate date]
toQueue:[NSOperationQueue mainQueue]
withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (error != nil && error.code == CMErrorMotionActivityNotAuthorized) {
// The app isn't authorized to use motion activity support.
This method will allow you to notify the user if the app isn't authorized to access Core Motion data. Simply create a CMPedometer instance called stepCounter and run the method above.
I lack of information in what is exactly happening when my application runs in the background and the screen is locked. Some setup.
I have created a CLLocationManager and started it, also I have registered a handler from altimeter updates. Of course background modes are on and selected background fetch and location updates, added in the Plist the required keys. What I care most is to get the location update and also do some work in the altimeter update handler. In the handler I have some logic and then I use NSURLSession to POST the values in my REST HTTP server.
From my testings I've noticed that since the user started the application allowed location updates and I started the location manager and altimeter updates my altimeter handler is invoked every second for about 20-25 seconds so I get my readings in the server.
I also noticed that if I open the application but don't initiate the whole process in the ViewController but simulate background fetch it doesn't seem to invoke the altimeter handler. Why I can't start the location manager when the background fetch is simulated and never started so far?
What are the limitations when the screen is locked after 10-15 minutes, is Apple background modes not working when application is suspended?
It is not terminated by the system cause as soon as I unlock it it will send me some messages to the server, like it kept it is in memory paused and the handler will start again to update. Also I found this article here, http://mobileoop.com/background-locatio ... -for-ios-7 claiming this is working for 3 hours, I don't like the solution but I was about to try, though it seems that it doesn't work for everyone the same, so I have doubts. He is using the UIBackgroundTaskIdentifier which I've seen in RW article before, http://www.raywenderlich.com/29948/back ... ng-for-ios, but haven't embedded yet cause I'm not sure if you are anyway able to send POST requests and updates when the screen is locked "forever" and maybe that time is sufficient if system won't allow you to do it anyway.
If the application stops responding, meaning the handler is not invoked, if I simulate background fetch the handler run 2-3 times but doesn't initiate the NSURLSession POST request.
The code.
// WEELog is just a wrapper function, you could change it to NSLog removing the log level
- (void) commonInitialization
{
self.readingsModel = [WEEReadingsModel new];
self.locationManager = [CLLocationManager new];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
self.locationManager.distanceFilter = 500;
if ([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)])
{
WEELog(DebugLogLevel, #"I should request location now");
[self.locationManager requestAlwaysAuthorization];
}
self.altimeter = [CMAltimeter new];
self.isAltimeterAvailable = [CMAltimeter isRelativeAltitudeAvailable];
}
- (void) start:(void (^)(UIBackgroundFetchResult))completionHandler
{
UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
if (self.altimeter == nil ||
self.locationManager == nil)
{
[self commonInitialization];
[self.locationManager startUpdatingLocation];
if (self.isAltimeterAvailable)
{
[self.altimeter startRelativeAltitudeUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAltitudeData *altitudeData, NSError *error)
{
UIBackgroundFetchResult fetchResult = error == nil ? UIBackgroundFetchResultNoData : UIBackgroundFetchResultFailed;
if (error == nil)
{
double previousPressureInInchesOfMercury = self.readingsModel.pressureInInchesOfMercury ? self.readingsModel.pressureInInchesOfMercury.doubleValue : 0.0;
[self.readingsModel appendWithAltitudeData:altitudeData];
if (self.readingsModel.pressureInInchesOfMercury.doubleValue != previousPressureInInchesOfMercury)
{
fetchResult = UIBackgroundFetchResultNewData;
WEELog(DebugLogLevel, #"New readings to save %#", self.readingsModel.toJSONString);
[[WEEServiceRepository sharedInstance] sendReadings:self.readingsModel withCompletionHandler:^(NSError *error, NSURLResponse *response)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
WEELog(DebugLogLevel, #"Called my completion handler: %#", [NSString stringWithFormat:#"%li - %#", (long)httpResponse.statusCode, [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode]]);
}];
}
}
else
{
WEELog(ErrorLogLevel, #"Error in altimeter handler: %#", error);
fetchResult = UIBackgroundFetchResultFailed;
}
}];
}
}
- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
if (location != nil)
{
WEELog(DebugLogLevel, #"Location update");
[self.readingsModel appendWithLocation:location];
}
}
// Web service call
- (void) sendReadings:(WEEReadingsModel*)readingsModel withCompletionHandler:(WebServiceCompletion)completionHandler
{
WEEReachability* reachability = [WEEReachability reachabilityForInternetConnection];
if (reachability.isReachable &&
readingsModel)
{
NSMutableURLRequest* urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:ServerUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSData* originalData = [[NSString stringWithFormat:#"data=%#", readingsModel.toJSONString] dataUsingEncoding:NSUTF8StringEncoding];
[urlRequest setHTTPBody:originalData];
if (!self.defaultSessionConfiguration)
{
self.defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.urlSession = [NSURLSession sessionWithConfiguration:self.defaultSessionConfiguration delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
}
NSURLSessionDataTask* dataTask = [self.urlSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
WEELog(DebugLogLevel, #"DataTask completion handler is invoked!");
if (completionHandler)
{
completionHandler(error, response);
}
}];
[dataTask resume];
}
}
I haven't tried using silent push notifications yet, do you think it would be a proper solution to wake up my app even if the app is locked and as I know it will run some code even if the app is closed right?
So I guess the information I miss most is for how much time the system will let me work with these APIs when I'm trying to use the in location updates, altimeter updates in the background and when the screen is locked? Is it specific or the system will decide how long? Is there a way I miss to make my app executing this code for ~hours?
Start Music/Audio(may be silent) in Background. This will keep your application awake, so that you can achieve whatever you want.
EKEventStore *eventStore = [[UpdateManager sharedUpdateManager] eventStore];
if ([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if (granted)...
I want to ask the user for permission to add an event to his calendar. After it's granted do I need to ask for permission again when I want for example to remove an event (in another session after the app was closed and reopened) or is it just a want time thing?
If it's a one time thing, can I just put it in ViewDidLoad at first lunch just to "get rid of it" ?
You only need to call it once:
BOOL needsToRequestAccessToEventStore = NO; // iOS 5 behavior
EKAuthorizationStatus authorizationStatus = EKAuthorizationStatusAuthorized; // iOS 5 behavior
if ([[EKEventStore class] respondsToSelector:#selector(authorizationStatusForEntityType:)]) {
authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
needsToRequestAccessToEventStore = (authorizationStatus == EKAuthorizationStatusNotDetermined);
}
if (needsToRequestAccessToEventStore) {
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
// You can use the event store now
});
}
}];
} else if (authorizationStatus == EKAuthorizationStatusAuthorized) {
// You can use the event store now
} else {
// Access denied
}
You shouldn't do that on the first launch, though. Only request access when you need it and that isn't the case just until the user decides to add an event.