CMAltimeter callback never fires - ios

Using my 6+ I've been trying to read the relative altitude and pressure using CoreMotion's new CMAltimeter. However the callback is never firing. I have a very similar setup which instead uses the accelerometers, gyros, and magnetometers. They all seem to work fine.
Was wondering if anyone out there has managed to get a reading?
- (void)viewDidLoad {
[super viewDidLoad];
if([CMAltimeter isRelativeAltitudeAvailable]){
CMAltimeter *altimeterManager = [[CMAltimeter alloc]init];
[altimeterManager startRelativeAltitudeUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
// This never fires.
NSString *data = [NSString stringWithFormat:#"Altitude: %f %f", altitudeData.relativeAltitude.floatValue, altitudeData.pressure.floatValue];
NSLog(#"%#", data);
self.altimeterLabel.text = data;
}];
NSLog(#"Started altimeter");
self.altimeterLabel.text = #"-\n-";
} else {
NSLog(#"Altimeter not available");
}
}
I've tried taking this on a quick walk, but there's only one story of altitude to lose/gain around here.

I'm pretty embarrased to answer my own question with such a huge oversight.
In the original post I had the CMAltimiter declared in the scope of viewDidLoad, thus it goes out of scope and is deallocated. I moved it to be an iVar and the callback now fires.
#import "ViewController.h"
#import CoreMotion;
#interface ViewController ()
#property (nonatomic, strong) CMAltimeter *altimeterManager;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if([CMAltimeter isRelativeAltitudeAvailable]){
self.altimeterManager = [[CMAltimeter alloc]init];
[self.altimeterManager startRelativeAltitudeUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
// This now fires properly
NSString *data = [NSString stringWithFormat:#"Altitude: %f %f", altitudeData.relativeAltitude.floatValue, altitudeData.pressure.floatValue];
NSLog(#"%#", data);
self.altimeterLabel.text = data;
}];
NSLog(#"Started altimeter");
self.altimeterLabel.text = #"-\n-";
} else {
NSLog(#"Altimeter not available");
}
}

You need to call [altimeterManager stopRelativeAltitudeUpdates]; for the references to be released to the dispatch queue.

Related

Background fetch and refresh completed after viewDidLoad in iOS 10

I'm trying to implement background fetch as well as refresh in iOS 10.
I'm using XML parsing to parse the data and then storing it in a file in the document's directory. For parsing XML I'm using a custom class (XMLParser) that confirms the NSXMLParserDelegate protocol.
The background fetch works fine. But I'm having problems in displaying the refreshed data, both when I click on the refresh button as well as in viewDidLoad.
I'm calling the refreshData method in viewDidLoad.
Here's how far I've gotten.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//--Set background fetch--//
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
...
#pragma mark Background data fetch methods
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSDate *fetchStart = [NSDate date];
ArtsViewController *artsViewController = (ArtsViewController *)self.window.rootViewController;
[artsViewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}];
}
ArtsViewController.h
#interface ArtsViewController : UIViewController <UIPageViewControllerDataSource>
#property BOOL newsAvailable;
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; // No problems here
#end
ArtsViewcontroller.m
#interface ArtsViewController ()
#property (nonatomic, strong) NSArray *arrNewsData;
-(void)refreshData;
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray;
#end
...
#implementation ArtsViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self refreshData];
//--Load the file that saves news--//
[self loadNews];
if (_newsAvailable == YES)
{
[self setupPageViewController];
}
else
{
[self showNoNewsMessage];
}
}
...
#pragma mark Data Fetch methods
-(void)refreshData{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
[self performNewFetchedDataActionsWithDataArray:dataArray];
}
else{
NSLog(#"%#", [error localizedDescription]);
}
}];
}
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray{
// 1. Initialize the arrNewsData array with the parsed data array.
if (self.arrNewsData != nil) {
self.arrNewsData = nil;
}
self.arrNewsData = [[NSArray alloc] initWithArray:dataArray];
// 2. Write the file and reload the view.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docDirectory = [paths objectAtIndex:0];
NSString * newsFilePath = [NSString stringWithFormat:#"%#",[docDirectory stringByAppendingPathComponent:#"arts2"]]; // NewsFile
if (![self.arrNewsData writeToFile:newsFilePath atomically:YES]) {
_newsAvailable = NO;
NSLog(#"Couldn't save data.");
}
else
{
_newsAvailable = YES;
NSLog(#"Saved data.");
[self viewWillAppear:YES];
}
}
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
NSDictionary *latestDataDict = [dataArray objectAtIndex:0];
NSString *latestTitle = [latestDataDict objectForKey:#"title"];
NSDictionary *existingDataDict = [self.arrNewsData objectAtIndex:0];
NSString *existingTitle = [existingDataDict objectForKey:#"title"];
if ([latestTitle isEqualToString:existingTitle]) {
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"No new data found.");
}
else{
[self performNewFetchedDataActionsWithDataArray:dataArray];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New data was fetched.");
}
}
else{
completionHandler(UIBackgroundFetchResultFailed);
NSLog(#"Failed to fetch new data.");
}
}];
}
...
#pragma mark IBActions
- (IBAction)reloadNews:(UIBarButtonItem *)sender
{
[self viewDidLoad];
}
I've debugged the application and found that after viewDidLoad
completes execution, the data file is written but the view isn't
updated. I've also tried calling the refreshData method in the main
thread, but there's no change.
after viewDidLoad is complete the showNoNewNews method is called.
I'm suspecting that my logic isn't wrong but implementation is. Threads at play here..
Any help would be appreciated.
Update:
Hope this helps those with similar problems...
I moved the logic of viewDidLoad to a different method, called the method for the first time in viewDidLoad and again in refreshData, after
[self performNewFetchedDataActionsWithDataArray:dataArray];

Send command and wait for reply - Wait for delegate in Obj-C

My goal is to achieve synchronized communication to custom Device i.e. next command can be send only when reply is received. Now I'm doing it in this way
Device class implements DeviceDelegate protocol
//Device.h
#class Device;
#protocol DeviceDelegate <NSObject>
- (void)didReciveReplyWithData:(NSData *)data;
#end
#interface Device : NSObject {}
In DeviceViewController implementation:
#interface DeviceViewController()
{
BOOL waitingForReply = false;
}
#end
#implementation DeviceViewController
- (void)sendCommandWithData:(NSData *)data
{
if ( waitingForReply == false)
{
//send command code
waitingForReply = true;
}
}
- (void)didReciveReplyWithData:(NSData *)data
{
//code
waitingForReply = false;
}
#end
but I wish to do it in more elegant way i.e. by using GCD (semaphores?) with blocks (completionHandler?). Any ideas?
PS. Sorry, but I forgot to mention: all commands sended to device while
waitingForReply = true
should be ignored!!!.
Possibly the best approach here would be to create a queue of commands with NSOperationQueue.
Since, presumably, the communication with the device is asynchronous you will have to subclass NSOperation to encapsulate the communication.
#interface DeviceCommandOperation : NSOperation <DeviceDelegate>
#property (nonatomic, assign) BOOL waitingForReply;
#property (nonatomic, copy) NSData *dataToSend;
#property (nonatomic, copy) NSData *dataReceived;
#end
#implementation DeviceCommandOperation
- (instancetype)initWithData:(NSData *)dataToSend
{
self = [super init];
if (self)
{
_dataToSend = [dataToSend copy];
}
return self;
}
- (void)setWaitingForReply:(BOOL)waitingForReply
{
if (_waitingForReply != waitingForReply)
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_waitingForReply = waitingForReply;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)start
{
self.waitingForReply = YES;
// Simulate sending a command and waiting for response.
// You will need to replace this with your actual communication mechanism.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// In reality this call would presumably come from the Device
[self didReceiveReplyWithData:someData];
});
}
- (void)didReceiveReplyWithData:(NSData *)data
{
self.dataReceived = data;
self.waitingForReply = NO;
}
#pragma mark - NSOperation
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
return _waitingForReply;
}
- (BOOL)isFinished
{
return !_waitingForReply;
}
#end
This operation could then be used from your DeviceViewController (it would probably be better architecturally to have this responsibility elsewhere but that's not the topic of this question).
#interface DeviceViewController ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
#implementation DeviceViewController
- (NSOperationQueue *)operationQueue
{
if (_operationQueue == nil)
{
_operationQueue = [[NSOperationQueue alloc] init];
}
return _operationQueue;
}
- (void)sendNextCommand
{
NSData *data = // Get data for the next command
[self sendCommandWithData:data];
}
- (void)sendCommandWithData:(NSData *)data
{
NSLog(#"Queueing operation");
DeviceCommandOperation *operation = [[DeviceCommandOperation alloc] initWithData:data];
// The operation's completionBlock gets called on a background queue
[operation setCompletionBlock:^{
NSLog(#"DeviceCommandOperation completed");
// Process operation.dataReceived
[self sendNextCommand];
}];
[self.operationQueue addOperation:operation];
}
#end
This approach will allow you to determine what (if any) command to send next, based on the reply to the previous command.
If you know all of the "commands" you will want to send initially and don't need finer grained control you could create instances of DeviceCommandOperation for each command, set the queue's maxConcurrentOperationCount to 1, and add each DeviceCommandOperation to the queue (in the order you want them to be processed).

Pausing, resuming and resetting steps counting in iOS Core Motion

I am writing an iOS app where it counts the steps taken by the user when activated using a button. I am able to count the steps now, but I would like to be able to pause and reset the steps counter by user request. I am not that experienced with XCode, so there might be an easy way to do it. I used a code similar to one available on Stackoverflow:
#import "ViewController.h"
#import "DTStepModelController.h"
#import <CoreMotion/CoreMotion.h>
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *stepsCountingLabel; #property (nonatomic, strong) CMStepCounter *cmStepCounter;
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
#implementation ViewController
{
DTStepModelController *_stepModel;
}
- (NSOperationQueue *)operationQueue
{
if (_operationQueue == nil)
{
_operationQueue = [NSOperationQueue new];
}
return _operationQueue;
}
- (void)updateStepCounterLabelWithStepCounter:(NSInteger)countedSteps
{
self.stepsCountingLabel.text = [NSString stringWithFormat:#"%ld", (long)countedSteps];
}
- (IBAction)StartCountingSteps:(id)sender {
if ([CMStepCounter isStepCountingAvailable])
{
self.cmStepCounter = [[CMStepCounter alloc] init];
[self.cmStepCounter startStepCountingUpdatesToQueue:self.operationQueue updateOn:1 withHandler:^(NSInteger numberOfSteps, NSDate *timestamp, NSError *error)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateStepCounterLabelWithStepCounter:numberOfSteps];
}];
}];
}
}
Any insight, or suggessions?
I was able to find an answer for my question. Please note that my answer and the previous code in the question will not work if you're using iOS 8.2 or above as Apple discontinue supporting steps counting. In the new iOS version, you can query the M7 counter and save the value, store it, then subtract the new value from the old one.
Anyway, for the above code, you can stop the counter (PauseCounter method) but it will reset the counter to zero.
-(IBAction) PauseCounting: (id) sender {
[self.cmStepCounter stopStepCountingUpdates];
} - (IBAction) ResumeCounting: (id) sender {
[self.cmStepCounter startStepCountingUpdatesToQueue: self.operationQueue updateOn: 1 withHandler: ^ (NSInteger numberOfSteps, NSDate * timestamp, NSError * error) {
[
[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
[self updateStepCounterLabelWithStepCounter: numberOfSteps];
}
];
}];
}

Load another View after successful TouchID login

I played around a bit with TouchID and I have the following question:
After successful TouchID login, how do I present a new ViewController?
The code in the viewController.m is:
#import "ViewController.h"
#import LocalAuthentication;
#import "SVProgressHUD.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LAContext *context = [[LAContext alloc] init];
NSError *error;
// check if the policy can be evaluated
if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error])
{
NSLog(#"error:%#", error);
NSString *msg = [NSString stringWithFormat:#"Can't evaluate policy! %#", error.localizedDescription];
[SVProgressHUD showErrorWithStatus:msg];
return;
}
// evaluate
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Please login through TouchID"
reply:
^(BOOL success, NSError *authenticationError) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
[SVProgressHUD showSuccessWithStatus:#"Everything Worked!"];
//Code for new viewController should come here!
}
else {
NSLog(#"error:%#", authenticationError);
[SVProgressHUD showErrorWithStatus:[NSString stringWithFormat:#"FAILED! %#", authenticationError.localizedDescription]];
}
});
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
viewController.h is standart. Nothing changed in it.
Thanks for support :)
To present a view controller we normally use following methods
If we are using storyboard,then call following method
[self performSegueWithIdentifier:#"indentifierForViewController" sender:self];
If we are not using storyboard then we can use
NextTaskViewControler *add = [[NextTaskViewControler alloc]
initWithNibName:#"NextTaskViewController" bundle:nil];
[self presentViewController:nextTaskVC animated:YES completion:nil];
I suggest you to use UINavigationController, a specialized view controller that manages other view controllers to provide a hierarchical navigation for the user. Present a viewcontroller only for specific purpose such as presenting a photo with few actions in it.It's easy to maintain when view hierarchy becomes complex
Please go-through UINavigationController Refrence

Data from API call not filling inside of method

I decided to go with an embedded API call within a view controller, and I'm having trouble with the data reaching out before the API returns with the information.
How do I wait for the data to be returned before the view controller displays all the values as nulls?
Thanks for any help.
#import "BDChangeApproveController.h"
#import "BDItemChangeDetailAPI.h"
#interface BDChangeApproveController () <NSURLSessionDelegate>
#property (nonatomic, strong) NSURLSession *session;
#property (nonatomic, copy) NSArray *APIItem;
#end
#implementation BDChangeApproveController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)fetchFeedAPIChangeDetail
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:nil];
NSString *requestString = #"http://URL.com";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:req
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error){
NSDictionary *jsonObject1 = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
//NSLog(#"%#", jsonObject1);
self.APIItem = jsonObject1[#"CoDetail"];
NSLog(#"%#", self.APIItem);
}];
[dataTask resume];
}
//API authentication
- (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
NSURLCredential *cred =
[NSURLCredential credentialWithUser:#"demouser"
password:#"secret"
persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchFeedAPIChangeDetail];
self.title = #"Action";
self.coNumberLabel.text = self.itemAPI.changeOrder;
//self.recipeImageView.image = [UIImage imageNamed:self.recipe.image];
NSLog(#"testtttt");
NSMutableString *coDetailsText = [NSMutableString string];
coDetailsText =
[[NSMutableString alloc] initWithFormat:#"Review Change Order details bellow\n====================\n%# \n================== \nPlanned Start %#\n==================\nSubcategory: %#\n==================\nService: %#\n==================\nAssociated CIs: %#\n==================\nEnvironment CI: %#\n==================\nApproval Group: %#\n==================\nInitiator : %#\n==================\nCoordinator : %#\n==================\nRisk Level : %#\n==================\nPerforming Group : %#\n==================\nImplementation Plan : %#\n==================\nStatus : %#\n==================\nRecovery Plan : %#\n==================\n",
/*
self.item.title,
self.item.changeOrder,
self.item.subcategory,
self.item.assignmentGroup,
self.item.changeOrder,
self.item.subcategory,
self.item.assignmentGroup,
self.item.approverEid,
self.item.approverEid,
self.item.subcategory,
self.item.assignmentGroup,
self.item.title,
self.item.title,
self.item.title
*/
self.itemAPI.title,
self.itemAPI.plannedStart,
self.itemAPI.subcategory,
self.itemAPI.service,
self.itemAPI.associatedCi,
self.itemAPI.environment,
self.itemAPI.assignmentGroup,
self.itemAPI.initiator,
self.itemAPI.coordinator,
self.itemAPI.riskLevel,
self.itemAPI.performingGroup,
self.itemAPI.implementationPlan,
self.itemAPI.status,
self.itemAPI.recoveryScope
// self.item.valueInDollars,
// self.item.dateCreated,
// self.item.subcategory,
// self.item.service,
// self.item.associatedCIs,
// self.item.environment,
// self.item.approvalGroup,
// self.item.initiator,
// self.item.coordinator,
// self.item.riskLevel,
// self.item.performingGroup,
// self.item.implementationPlan,
// self.item.validationPlan,
//self.item.recoveryScope
];
self.coDetailsTextView.text = coDetailsText;
NSLog(#"overrrr");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
// Adding File
#import <Foundation/Foundation.h>
#import "BDItemChangeDetailAPI.h"
#interface BDChangeApproveController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *coNumberLabel;
#property (weak, nonatomic) IBOutlet UITextView *coDetailsTextView;
#property (nonatomic, strong) BDItemChangeDetailAPI *itemAPI;
#end
Looks like you're doing something asynchronously and expecting it to act synchronously.
Look at the below piece of code you're using in viewDidLoad:
[self fetchFeedAPIChangeDetail]; // this takes some time to complete
self.title = #"Action"; // this happens immediately after the above starts, not ends
self.coNumberLabel.text = self.itemAPI.changeOrder; // so does this, so at this point itemAPI is probably nil
fetchFeedAPICheckDetail is an asynchronous process, so it might take a few seconds to complete, whereas setting the title and coNumberLabel happens immediately after, so you don't yet have the itemAPI information from the URL Session. Your code doesn't wait for fetchFeedAPIChangeDetail to be done with the request before continuing onto the next line.
Declare a function
-(void)refreshTextView
{
// set your text view text with the api result here
self.coNumberLabel.text = self.itemAPI.changeOrder;
}
Call this method in at the end of your request block after NSLog(#"%#", self.APIItem);
Like this
[self performSelectorOnMainThread:#selector(refreshTextView) withObject:nil waitUntilDone:NO];
When you will call this method in the block it will be called and will update the textview text with the api results. The block is asynchronous and so when you update your textview later it doesn't gets updated because the block has not yet completed its function and the array is still nil but calling this method in the block would assure that the result gets assigned and updated in the textview.
UPDATE
and you do have a typo here
#property (nonatomic, copy) NSArray *APIItem;
you are naming it with the class name thats a conflict.
Name it something else like #property (nonatomic, copy) NSArray *apiItem; Actually u have a class name APIItem declared because of which a conflict occurs and that is the very reason it is marked blue in xcode. look all class names are marked blue in your code. Like NSArray , NSURLSession etc –

Resources