Prompt the user to authorise the application to share types in HealthKit - objective-c

When first calling requestAuthorizationToShareTypes:readTypes:completion: of the HKHealthStore with the set of HKQuantityType I want permissions to, I can see this modal view requesting authorization from the user to read and share every type of object for which the application may require access.
I'm trying to figure out if there is a way to prompt the user this modal view, besides the first time i'm calling: requestAuthorizationToShareTypes:readTypes:completion:.
I tried calling the requestAuthorizationToShareTypes:readTypes:completion: every time with the same set but after the first call its not prompting anymore. When trying to change the set with a new type that wasn't there before I can successfully prompt this screen, but I don't think calling this method each time with a new HKQuantityType in the set is the right way (as there is a limit to the amount of types exists).
Is it even possible?
Thanks for any help whatsoever.
UPDATE
I'll add some code snippet of the call:
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. The error was: %#.", error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Update the user interface based on the current user's health information.
});
}];
where writeDataTypesz and readDataTypes are NSSet returned from the following methods:
// Returns the types of data that I want to write to HealthKit.
- (NSSet *)dataTypesToWrite
{
HKQuantityType *heightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
return [NSSet setWithObjects: heightType, weightType, nil];
}
// Returns the types of data that I want to read from HealthKit.
- (NSSet *)dataTypesToRead
{
HKQuantityType *heightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
HKCharacteristicType *birthdayType = [HKCharacteristicType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth];
HKCharacteristicType *biologicalSex = [HKCharacteristicType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex];
return [NSSet setWithObjects:heightType,weightType,birthdayType,biologicalSex, nil];
}
Changing the set to include NEW types each time I call requestAuthorizationToShareTypes:readTypes:completion: results in opening this view with all the types that I ever asked permissions to (not necessarily in this specific call):

By design, it is not possible to re-prompt the user for authorization. If you would like the user to consider changing their authorizations for your app, ask them to go to Settings or the Health app to do so.

By the time being we can't do it. However, it makes sense to be able to do so because switching the access rights is not as simple as tap the "ok" or "cancel" button. You may assume that everyone knows how to tap the switch and turn things on, but trust me, you'll be wrong if you assume that way.
I'd file a bug in apple's radar to let them know that their design is not flawless. We should be able to prompt the user again. If user is annoyed, he can always delete our app, and I feel it's much better than reading a really long "how to enable the access in Health.app" list and getting confused, and that's what a normal user would do.

Related

parse.com getting pointer data from currentUser

i have a pointer key in PFUser and I'm trying to retrieve the object it's pointing to. I've seen many examples about querying for it but there shouldn't be any need for that, since parse has the fetch method and it's a pointer of PFUser class, so I'm using this:
PFObject *menu = [[[PFUser currentUser] objectForKey:#"menuID"] fetchIfNeeded];
I know my current user has an object being pointed to in that key but i get a null menu object all the time
Wain was correct in saying that you need to fetch the currentUser. However, you have to keep in mind that we're working with multiple threads if you want to use fetchInBackground. To keep in on a single thread, simply use [[PFUser currentUser] fetch], but keep in mind that this can cause hanging or blocking for your user when internet connection is bad. Below is a sample of how to use this more effectively. (Same issue with the fetch vs. fetchInBackground for the menu) We have to fetch the menu as well, since it is a pointer and so the currentUser will only fetch the pointer and not the whole object.
[[PFUser currentUser] fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(!error){
PFObject *menu = object[#"menuID"];
[menu fetch];
// Execute any code that needs the menu here, or store it in your viewController (or other class variable) if you need to save it for later
// Alternately, you could use this:
dispatch_async(dispatch_get_main_queue(), ^{
[menu fetchInBackgroundWithBlock:^(PFObject *fetchedMenu, NSError *menuError) {
if(!menuError){
// Execute any code that needs the menu here, or store it
} else {
NSLog(#"%#", menuError);
}
}];
});
} else {
NSLog(#"%#", error);
}
}];
By default currentUser doesn't have any custom added columns of data populated. You need to fetch the user to download that data and then you can use it locally.
Alternatively your oils us cloud code and save a network request.

Unable to login PFUser or create PFObjects in OSX

Edit 1
Actually there's something more serious going on here as i am also not able to create and test objects.
So i have imported #import
Then i am calling the following from applicationDidFinishLaunching:
- (void)initParse {
[Parse setApplicationId:#"-----"
clientKey:#"-----"];
PFObject *testObject = [PFObject objectWithClassName:#"TestObject2"];
testObject[#"foo"] = #"barking";
[testObject saveInBackground];
}
With the correct application and client keys, but i am getting the following error logged in the console:
[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]
No idea why this is not working??
Thanks
Gareth
Ok, bit stumped on this one.
I have an OS X application, and i am trying to login a user using the following method in my viewController class, as follows:
- (IBAction)loginButton:(id)sender {
[PFUser logInWithUsernameInBackground:#"myname" password:#"mypass"
block:^(PFUser *user, NSError *error) {
if (user) {
// Do stuff after successful login.
NSLog(#"User Logged In");
} else {
// The login failed. Check error to see why.
NSLog(#"User Login failed");
}
}];
}
I have manually entered the username and password strings whilst troubleshooting.
Irrelevant of whether i set them to valid credentials or not the block never get's executed, if i set breakpoints none of them ever get hit, and none of the messages get logged.
Am i missing something? I don't see why this is not working. I have an iOS app setup with the same app id and key and that logs in using the same credentials fine, but this one will not.
Anyone got any ideas what's going on here?
Thanks
Gareth
Ok so for anyone else who hits this, it's because i wasn't including the Bolts.framework in my application.
Not mentioned anywhere in the Parse docs that this framework is needed. Dropped it in and like magic it started working.
Very unhelpful that there was nothing logged when it wasn't loaded from the Parse framework . . .
That's 8 hours of my life i'm never getting back!
Cheers
Gareth

Mac app NSArrayController bindings not autosaving Core Data

I was under the impression that when using bindings (been following this tutorial despite being outdated. http://cocoadevcentral.com/articles/000085.php - You can use it to see what I'm doing) the Persistent Store would automagically save the changes you make. In fact, though it was hours ago and I wouldn't be surprised if I'm now going mad, I got it working, and when I made a change it would persist on rebuilding the app.
However, the test app I've built following the tutorial no longer saves and despite showing the changes I make within the app, they disappear once I re-run the app. I've been checking the Core Data debug menu and nothing happens when I press the "+" button which is set up to the "Add" method of my NSArrayController. I know it's accessing my data model too as my textField for the Title (again, see the tutorial so you know what I'm referring to) adopts the default text I put in the DataModel section. The only thing missing therefore is the actual saving.
So my real question is, based on the tutorial, what part of the bindings actually makes the managedObjectContext save? Is there a flag or something that isn't checked?
I don't know if it's important or not, but there were differences between the tutorial and my project, mainly that the NSArrayControllers are bound to "App Delegate"with a Model Key Path of "self.managedObjectContext". Also, I removed all the relationships in an attempt to whittle down the issue.
Any help would be greatly appreciated.
Regards,
Mike
UPDATE: Here are some pictures that show the bindings.
How I set up the NSArrayController:
Here is how is how my Data Model Looks:
Lastly, this is how I set up the TextFields to update the NSArrayControllers:
I hope this helps to get a an ideas as to the set up.
Thanks,
Mike
Could you check to make sure you've copied all the Core Data boiler-plate code from the source code of the tutorial you mentioned.
Specifically this part in the App Delegate:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSError *error;
NSManagedObjectContext *context;
int reply = NSTerminateNow;
context = [self managedObjectContext];
if (context != nil) {
if ([context commitEditing]) {
if (![context save:&error]) {
// This default error handling implementation should be changed to make sure the error presented includes application specific error recovery. For now, simply display 2 panels.
BOOL errorResult = [[NSApplication sharedApplication] presentError:error];
if (errorResult == YES) { // Then the error was handled
reply = NSTerminateCancel;
} else {
// Error handling wasn't implemented. Fall back to displaying a "quit anyway" panel.
int alertReturn = NSRunAlertPanel(nil, #"Could not save changes while quitting. Quit anyway?" , #"Quit anyway", #"Cancel", nil);
if (alertReturn == NSAlertAlternateReturn) {
reply = NSTerminateCancel;
}
}
}
} else {
reply = NSTerminateCancel;
}
}
return reply;
}
If it's there, changes will be saved when the app is terminated normally. Pressing the 'stop' button in Xcode will terminate the app immediately, without going through the method mentioned above.
My guess is that you are not going mad, but first exited the app properly and have been pressing the 'stop' button later ;).

KIF: How to auto-run/stress test an iOS app to find the cause of a rare UI bug?

Note: I added kif to the title just for search indexing puposes, considering that most of the answer turned out to discuss it
I'm looking for something like selenium for iOS, basically a test-automation/unit test framework that can run a certain UI scenario many many times until it crashes, which would help me narrow down the cause of a UI bug that happens very rarely and randomly.
(and by the way, I've NSLogged every single line of code of datasource/table interaction and spent hours analyzing the potential cause.. but found nothing conclusive.. again this bug very rarely happens).
I looked at some of the unit testing frameworks in iOS, but they seem to be so many. I'm not sure which to pick. Also my reference to selenium is based on conjecture, as I've worked with QA folks who've used Selenium in large web projects in the past (and i'm assuming that there must be something similar for iOS).
Now that I'm a one man team working on an iOS project, I'm gonna have to put a QA hat on and figure this bug out.
I'm facing a classic bug that happens when there is a discrepancy between the actual number of rows inserted in a UITableView and the number of rows that the datasource delegate returns. This is the error message:
*** Assertion failure in -[UITableView
_endCellAnimationsWithContext:] Exception in insertRows: Invalid
update: invalid number of rows in section 0.
The number of rows contained in an existing section after the update (2) must be equal to
the number of rows contained in that section before the update (2),
plus or minus the number of rows inserted or deleted from that section
(1 inserted, 0 deleted) and plus or minus the number of rows moved
into or out of that section (0 moved in, 0 moved out).
I click on a UITableViewCell that takes me into another UITableView. Sometimes it works
and sometimes (very rarely) it doesn't (with the above error):
update:.. i've added example code about KIF 2.0 at the bottom after divider.. for those who are more interested in KIF than the specific problem i'm facing:
After some research and experimenting.. I've narrowed down my options to two test-automation libraries:
Frank and KIF. I ultimately decided to use KIF while borrowing cucumber's Gherkin syntax to describe my unit tests.
The reason I chose KIF (rather than Frank) was that KIF is 100% obj-c based, rather than using ruby as well as was the case with Frank. So setting up is simpler, and it was more applicable to my narrow test case requirement. That being said, I admit Frank would be more useful if my application was more complicated (ie using intput from multiple servers etc). You can see the last quarter of this excellent presentation to learn more about the pros and cons of KIF, Frank and other automation-testing frameworks including Apple's own UI Automation.
After using KIF, I found the bug causing the error above, and I could reproduce it using KIF 100% of the time! The reason why it happened so rarely was because it happened only when I tapped through the screens really fast.. and since KIF automates the steps.. it does them at an incredibly fast speed.. which exposed the bug :).
So following will be a sample of the code I used for testing.. this is just to give you a quick feel of what KIF (and Gherkin) can do for you:
in one file I specify the scenarios I want to run:
- (void)initializeScenarios;
{
[self addScenario:[KIFTestScenario scenarioToCompleteSignInAndLoadInbox]];
[self addScenario:[KIFTestScenario scenarioToFillAttachmentsWithData]];
[self addScenario:[KIFTestScenario scenarioToViewAndLoadFileBucket]];
[self addScenario:[KIFTestScenario scenarioToViewAndLoadFileBucketSubView]];
}
each scenario maps to steps (to understand more about the gherkin syntax -and behavioral driven development, which is based on test driver development, I strongly recommend to read this excellent book about cucumber):
/* #given the application is at a fresh state
#and the user already has an imap email account with a valid username/pwd
#then the user can successfully log in
#and the inbox view will be loaded
#and the inbox will get loaded with the latest batch of emails in the user inbox
*/
+ (id)scenarioToCompleteSignInAndLoadInbox
{
KIFTestScenario *scenario =
[KIFTestScenario scenarioWithDescription:#"Test that a user
can successfully log in."];
[scenario addStepsFromArray:[KIFTestStep stepsCompleteSignInAndLoadInbox]];
return scenario;
}
/* #given that the user is already signed in
#and the user has already downloaded their folders
#then the user can click on the folders view
#and the user can click on the 'attachments' remote folder
#and the latest batch from the 'attachments' remote folder will download
*/
+ (id)scenarioToFillAttachmentsWithData {
KIFTestScenario* scenario =
[KIFTestScenario scenarioWithDescription:#"Test that we can view the
attachments folder and fill
it with data."];
[scenario addStepsFromArray:[KIFTestStep stepsToFillAttachmentsWithData]];
return scenario;
}
/* #given that the user is already signed in
#and the user has already downloaded their folders
#and the user has already downloaded attachments
#then the user can click on inbox menu button
#and the user can click on folder list menu button
#and the user can click on the file bucket icon (on the account list view)
#and the data for the file bucket is fetched from the dbase
#and the file bucket view displayes the attachments
*/
+ (id)scenarioToViewAndLoadFileBucket {
KIFTestScenario *scenario =
[KIFTestScenario scenarioWithDescription:#"Test that a user can successfully
view and load
file bucket parent view"];
[scenario addStepsFromArray:[KIFTestStep stepsToViewAndLoadFileBucketPage]];
return scenario;
}
/* #given that the user is already signed in
#and the user has already downloaded their folders
#and the user has already downloaded attachments
#and the user has already opened file bucket view
#then the user can click on a random row in the file bucket view table
#and the subview will retrieve data from the dbase pertaining to that row
#and the subview will display the data in the uitableview
*/
+ (id)scenarioToViewAndLoadFileBucketSubView {
KIFTestScenario *scenario =
[KIFTestScenario scenarioWithDescription:#"Test that a user can successfully
view and load filet
bucket sub view"];
[scenario addStepsFromArray:[KIFTestStep stepsToViewAndLoadFileBucketSubPage]];
return scenario;
}
and steps are defined using KIF's UI automation methods (this is just one example):
// this step assumes there is an attachment folder that contains emails with attachments
+ (NSArray *)stepsToFillAttachmentsWithData {
NSMutableArray* steps = [#[] mutableCopy];
[steps addObject:
[KIFTestStep stepToTapViewWithAccessibilityLabel:#"InboxMenuButton"]];
NSIndexPath* indexPath =
[NSIndexPath indexPathForRow:remoteAttachmentFolderNumber inSection:0];
KIFTestStep* tapAttachmentRowStep =
[KIFTestStep stepToTapRowInTableViewWithAccessibilityLabel:
#"attachments" atIndexPath:indexPath];
[steps addObject:[KIFTestStep stepToWaitForNotificationName:
(NSString *)kBeganSyncingOlderEmails object:nil
whileExecutingStep:tapAttachmentRowStep]];
[steps addObject:tapAttachmentRowStep];
[steps addObject:
[KIFTestStep stepToWaitForViewWithAccessibilityLabel:#"attachments"]];
KIFTestStep *fillingInboxStep =
[KIFTestStep stepToWaitForNotificationName:
(NSString *)kOldMailBatchDelivered object:nil];
[fillingInboxStep setTimeout:kSpecialTimeoutForLongTests];
[steps addObject:fillingInboxStep];
return steps;
}
KIF 2.0 sample code:
KIF 2.0 uses Xcode 5's all new test navigator.. which is a huge improvement than what KIF 1.0 was doing.. now your tests feel a lot more organic and natural than the past.. (ie it goes in real time.. rather than creating scenarios that run in the future etc).. you even get to test each one with a play button etc.. you should try it out.
here are some examples (again using gherkin syntax):
#import <KIF/KIF.h>
#import "KIFUITestActor+EXAdditions.h"
#import "KIFUITestActor+UserRegistration.h"
#interface LoginTests : KIFTestCase
#end
#implementation LoginTests
- (void)testReset {
[tester flushDbase];
[tester reset];
}
/* #given that the app is in a fresh clean state
#and that no one has ever registered with the server
#then the user can register their themselves with the server
#and immediately start with the rider's map
#and their location on the map shows
*/
- (void)testRegistration
{
[tester flushDbase];
[tester reset];
[tester singleUserRegistration];
[tester showUserCurrentLocationOnMap];
}
/* #given that the user has already registered with the server
#and the user is not currently logged in
#then the user can login using their user name and password
#and immediately start with the rider's map
#and their location on the map shows
*/
- (void)testSuccessfulLogin
{
[tester reset];
[tester login];
[tester showUserCurrentLocationOnMap];
}
/* #given that the user has already registered
#and that the user is already logged in before app launch
#then the user starts on the map view with the location visible
#and the button prompts them to set pick up location
*/
- (void)testStartOfApplication {
[tester showUserCurrentLocationOnMap];
[tester showsPickUpButton];
}
#end
here is the implementation of some of the test cases in the category files:
- (void)reset
{
[self runBlock:^KIFTestStepResult(NSError **error) {
BOOL successfulReset = YES;
// Do the actual reset for your app. Set successfulReset = NO if it fails.
AppDelegate* appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate resetApp];
KIFTestCondition(successfulReset, error, #"Failed to reset some part of the application.");
return KIFTestStepResultSuccess;
}];
}
- (void)flushDbase {
[self runBlock:^KIFTestStepResult(NSError **error){
NSURL *url = [NSURL URLWithString:#"http://randomdomain.com/flush_db"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSError *connectionError = nil;
BOOL databaseFlushSucceeded = YES;
NSURLResponse *response;
NSData *resultData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&connectionError];
if (!resultData) {
databaseFlushSucceeded = NO;
KIFTestCondition(databaseFlushSucceeded, error, #"failed to connect to server!");
}
if (connectionError) {
databaseFlushSucceeded = NO;
KIFTestCondition(databaseFlushSucceeded, error, [NSString stringWithFormat:#"connection failed. Error: %#", [connectionError localizedDescription]]);
}
return KIFTestStepResultSuccess;
}];
}
- (void)navigateToLoginPage
{
[self tapViewWithAccessibilityLabel:#"login email"];
}
- (void)returnToLoggedOutHomeScreen
{
[self tapViewWithAccessibilityLabel:#"Logout"];
[self tapViewWithAccessibilityLabel:#"Logout"]; // Dismiss alert.
}

Prevent warning when NSDocument file is programmatically renamed

My application allows the user to rename documents that are currently open. This is trivial, and works fine, with one really annoying bug I can't figure out. When a file is renamed, AppKit (kindly) warns the user the next time they try to save the document. The user says "OK" and everything continues as normal. This makes sense when something external to the application changed the document, but not when it was actually done by the document itself.
The code goes something like this:
-(void)renameDocumentTo:(NSString *)newName {
NSURL *newURL = [[[self fileURL] URLByDeletingLastPathComponent]
URLByAppendingPathComponent:newName];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager moveItemAtURL:[self fileURL] toURL:newURL];
NSDictionary *attrs = [fileManager attributesForItemAtPath:[newURL path] error:NULL];
[self setFileURL:newURL];
[self setFileModificationDate:[attrs fileModificationDate]];
}
One would think that expressly setting the new URL and modification date on the document would be enough, but sadly it's not. Cocoa still generates the warning.
I've tried changing the order (setting the new URL on the document, THEN renaming the file) but this doesn't help.
I've also tried a fix suggested by a user on an old post over at CocoaDev:
[self performSelector:#selector(_resetMoveAndRenameSensing)];
Even this does not stop the warning however, and I'm guessing there has to be a proper way to do this using the documented API. How does Xcode handle things when a user clicks a file on the project tree and renames it to something else. It doesn't warn the user about the rename, since the user actually performed the rename.
What do I need to do?
There isn't much on this in the main docs. Instead, have a look at the 10.5 release notes: http://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKitOlderNotes.html%23X10_5Notes under the heading "NSDocument Checking for Modified Files At Saving Time"
(In the case of Xcode, it has a long history and I wouldn't be surprised if if doesn't use NSDocument for files within the project)
It is worth noting that moving a file does not change its modification date, so calling -setFileModificationDate: is unlikely to have any effect.
So one possibility could be to bypass NSDocument's usual warning like so:
- (void)saveDocument:(id)sender;
{
if (wasRenamed)
{
[self saveToURL:[self fileURL] ofType:[self fileType] forSaveOperation:NSSaveOperation delegate:nil didSaveSelector:nil contextInfo:NULL];
wasRenamed = NO;
}
else
{
[super saveDocument:sender];
}
}
Ideally you also need to check for the possibility of:
Ask app to rename the doc
Renamed file is then modified/moved by another app
User goes to save the doc
At that point you want the usual warning sheet to come up. Could probably be accomplished by something like:
- (void)renameDocumentTo:(NSString *)newName
{
// Do the rename
[self setFileURL:newURL];
wasRenamed = YES; // MUST happen after -setFileURL:
}
- (void)setFileURL:(NSURL *)absoluteURL;
{
if (![absoluteURL isEqual:[self fileURL]]) wasRenamed = NO;
[super setFileURL:absoluteURL];
}
- (void)setFileModificationDate:(NSDate *)modificationDate;
{
if (![modificationDate isEqualToDate:[self fileModificationDate]]) wasRenamed = NO;
[super setFileModificationDate:modificationDate];
}
Otherwise, your only other choice I can see is to call one of the standard save/write methods with some custom parameters that prompt your document subclass to move the current doc rather than actually save it. Would be trickier I think. Perhaps define your own NSSaveOperationType?
With this technique the doc system should understand that the rename was part of a save-like operation, but it would need quite a bit of experimentation to be sure.
Much inspired from #Mike's answer, I got the "moved to" message not to show up anymore by re-routing NSSaveOperation to NSSaveAsOperation. In my NSDocument subclass:
I overload saveDocumentWithDelegate:didSaveSelector:contextInfo: to determine the save URL and document type (assigning those to self); if the old fileURL exists, I move that to the new location
Inside saveDocumentWithDelegate:didSaveSelector:contextInfo: I redirect the call to [self saveToURL:self.fileURL ofType:self.fileType forSaveOperation:NSSaveAsOperation completionHandler: ...] instead of [super saveDocumentWithDelegate:didSaveSelector:contextInfo:]
This works for me.
Isn't it possible to programmatically answer the question for the user?
Or you can save immediately after renaming, this way a user gets every answer in one go.
I see that this problem is up and running for some time, so telling you to read the reference won't do any good i guess..
Hope i helped a little bit although it doesn't fix your problem directly