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“
Related
I am having an unexpected issue with a crash on my app which I am specifically struggling to debug because it occurs in background at a time determined by the iOS system. I have some capitalised comments to the code which show where the issue is being back traced to. I hope this is clear.
I believe it has to do with object deallocation.
I have tried using the __block before initialising the object but
this has not helped.
I have also tried dispatching the lines of code
in error to the main queue but that has not helped.
The actual crash is listed as AppName: __66-[BackgroundUpdateController initiateBackgroundHealthkitObservers]_block_invoke_5 + 160
I apologise if some of the code does not fit standard formatting and conventions. I am self taught from a variety of places and so do not have proper experience with code format.
Many Thanks
#import "BackgroundUpdateController.h"
NSUserDefaults *backgroundDefaults;
#implementation BackgroundUpdateController
-(id)init{
backgroundDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.HeartAnalyzer"];
return [super init];
}
-(void)initiateBackgroundHealthkitObservers{
// Check we should be running here
if(([backgroundDefaults integerForKey:#"sleepAnalysisEnabled"] != 1) || (![backgroundDefaults boolForKey:#"AutomaticSleepAdd"])) return;
// Initiate some variables, Use __block to ensure the backgroundHealthStore object does not get deallocated
__block HKHealthStore *backgroundHealthStore = [[HKHealthStore alloc] init];
HKQuantityType *activeEnergy = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
// Enable background delivery of active energy data from HealthKit
[backgroundHealthStore enableBackgroundDeliveryForType:activeEnergy frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
}];
// Now setup an HKOberverQuery which triggers hourly if there are new active energy data points in HealthKit
HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:activeEnergy predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
UIApplicationState state = [[UIApplication sharedApplication] applicationState];
if (state == UIApplicationStateBackground || state == UIApplicationStateInactive){// Only run when app is not in foreground
// Load some more variables with checks to ensure they are valid objects
NSDate *previousSavedDate = [backgroundDefaults objectForKey:#"DateBackgroundSleepLastSaved"];
if(previousSavedDate == nil) previousSavedDate = [NSDate distantPast];
NSDate *lastSleepCheck = [backgroundDefaults objectForKey:#"LastSleepCheck"];
if(lastSleepCheck == nil) lastSleepCheck = [NSDate distantPast];
// If the last save date was long enough ago and the last sleep check was long enough ago, proceed
if(([previousSavedDate timeIntervalSinceNow] < -(3600*18)) && ([lastSleepCheck timeIntervalSinceNow] < -(3600*2))){
[backgroundDefaults setObject:[NSDate date] forKey:#"LastSleepCheck"];
[backgroundDefaults setBool:NO forKey:#"BackgroundSleepFound"];
SleepTimesCalculator *sleepClass = [[SleepTimesCalculator alloc] init];
[sleepClass calculateSleepTimes:^{
NSLog(#"Background sleep time calculations complete");
if([backgroundDefaults boolForKey:#"BackgroundSleepFound"]){// Only continue is a sleep time was found
__block NSMutableArray *savedSleepObjects = [backgroundDefaults valueForKey:#"SleepTimesDataBase"];
if(savedSleepObjects.count > 0){
__block NSMutableDictionary *sleepObject = [savedSleepObjects objectAtIndex:0]; // THE __BLOCK USED TO PREVENT THE OBJECT BEING DEALLOCATED, STILL SEEMS TO BE BASED ON THE CRASH
NSDate *sleepStart = [NSDate dateWithTimeIntervalSinceReferenceDate:[[sleepObject valueForKey:#"CalculatedSleepTime"]integerValue]];// Get the sleep time start date object
NSDate *sleepEnd = [NSDate dateWithTimeIntervalSinceReferenceDate:[[sleepObject valueForKey:#"CalculatedWakeTime"]integerValue]];
NSInteger sleepSavedToHealth = [[sleepObject valueForKey:#"SavedToHealth"] integerValue];// Check its not already been saved by some other element of the app
if(sleepSavedToHealth != 1){
HKCategorySample *sleepSample = [HKCategorySample categorySampleWithType:[HKCategoryType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis] value:1 startDate:sleepStart endDate:sleepEnd];// Generate sleep object for HealthKit
[backgroundHealthStore saveObject:sleepSample withCompletion:^(BOOL success, NSError *error) {
if (!success) NSLog(#"Uncommon Error! saveObject:sleepSample");
else{
dispatch_async(dispatch_get_main_queue(), ^{// DISPATCH TO MAIN QUEUE AN ATTEMPTED FIX FOR CRASH
sleepObject = [savedSleepObjects objectAtIndex:0];// Choose the most recent sleep time to save
[sleepObject setValue:[NSNumber numberWithInteger:1] forKey:#"SavedToHealth"];// THIS IS WHERE THE 'Last Exception Backtrace (0)' ENDS UP
[savedSleepObjects replaceObjectAtIndex:0 withObject:sleepObject];// Replace the object which now has the 'Saved' tag
[backgroundDefaults setObject:[NSDate date] forKey:#"DateBackgroundSleepLastSaved"];// Save the data of the last time we reached this point
[backgroundDefaults setObject:savedSleepObjects forKey:#"SleepTimesDataBase"];// Save the sleep times back to the database
});
}
}];
}
completionHandler();// Call the completion handler as we've been throught the sleepObjects array
}
else completionHandler();// Call the completion handler anyway
}
else completionHandler();// Call the completion handler anyway
}];
}
else completionHandler();
}
}];
[backgroundHealthStore executeQuery:query];// Execute the HealthKit healthstore query
}
#end
Prefixing __block does not guarantees existence of an object for #"CalculatedSleepTime" key in sleepObject
I think you have misinterpreted how __block works. This will be a great guide.
On a quick overview of the code, it seems like [sleepObject valueForKey:#"CalculatedSleepTime"] is returning nil & without a nullability check you are trying to extract the integerValue
So, consider:
NSMutableDictionary *sleepObject = [savedSleepObjects objectAtIndex:0];
id calculatedSleepTime = [sleepObject valueForKey:#"CalculatedSleepTime"];
if(calculatedSleepTime){
NSDate *sleepStart = [NSDate dateWithTimeIntervalSinceReferenceDate:[calculatedSleepTime integerValue]];
}
And it looks like you also don't require the __block prefix in HKHealthStore *backgroundHealthStore = [[HKHealthStore alloc] init];
Is it possible to Access Default settings > TouchID & Passcode > iPhone Unlock toggle value.
As far as I know, it's not possible.
NO. there is no way to know if user has opted for using TouchID for Unlocking phone.
There is method canEvaluatePolicy: error:
But this tells you if TouchId is configured / Enabled or Not Configured/Not Enabled. If you want to check for Availability of touch Id for your app, you can use canEvaluatePolicy: error:
-(void)canEvaluatePolicy {
LAContext *context = [[LAContext alloc] init];
__block NSString *message;
NSError *error;
BOOL success;
// test if we can evaluate the policy, this test will tell us if Touch ID is available and enrolled
success = [context canEvaluatePolicy: <BR>LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error];
if (success) {
message = [NSString stringWithFormat:#"Touch ID is available"];
}
else {
message = [NSString stringWithFormat:#"Touch ID is not available"];
}
[super printMessage:message inTextView:self.textView];
}
you can find fully working code from developer.apple.com website:
https://developer.apple.com/library/content/samplecode/KeychainTouchID/Listings/KeychainTouchID_AAPLLocalAuthenticationTestsViewController_m.html
I don't know why you would want to know that however, you can always check if the device supports TouchID and if it has been setup by the user. You do this by creating an LAContext (Local Authentication Context) and calling the function canEvaluatePolicy:error:. That is all I think you can find out about the TouchID settings on a given iPhone through an app. I hope this helps a little :)
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 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
I'm creating a simple note application and I want to implement Reminders. The user would type a note, tap a button and it would set up a reminder in the Reminders app using the text. Is this possible, and if so, how do I do it? I have seen Apple's documentation on EventKit and EKReminders but it has been no help at all.
From the "Calendars and Reminders Programming Guide"? Specifically "Reading and Writing Reminders"
You can create reminders using the reminderWithEventStore: class method. The title and calendar properties are required. The calendar for a reminder is the list with which it is grouped.
Before you create a reminder, ask for permission:
In the .h:
#interface RemindMeViewController : UIViewController
{
EKEventStore *store;
}
and the .m, when you are going to need access to Reminders:
store = [[EKEventStore alloc] init];
[store requestAccessToEntityType:EKEntityTypeReminder
completion:^(BOOL granted, NSError *error) {
// Handle not being granted permission
}];
To actually add the reminder. This happens asynchronously, so if you try to add a reminder immediately after this, it will fail (crashes the app in my experience).
- (IBAction)addReminder:(id)sender
{
EKReminder *reminder = [EKReminder reminderWithEventStore:store];
[reminder setTitle:#"Buy Bread"];
EKCalendar *defaultReminderList = [store defaultCalendarForNewReminders];
[reminder setCalendar:defaultReminderList];
NSError *error = nil;
BOOL success = [store saveReminder:reminder
commit:YES
error:&error];
if (!success) {
NSLog(#"Error saving reminder: %#", [error localizedDescription]);
}
}