I am attempting to create a local calendar on iOS. I request access to EKEntityTypeEvent and have it granted, create the calendar from the EKEventStore (yes, the same instance I requested access from), find the EKSourceTypeLocal then set it on my new calendar. Calling saveCalendar:commit:error (with commit:YES) returns YES and there is no NSError. The resulting calendar has a calendarIdentifier assigned.
But then when I flip to the iOS Calendar app, my calendar is not there! I've tried on the iOS 7 and 8 simulators (after a "Reset content and settings...", so there's no iCloud configured) and on an iCloud-connected iPhone 5s with iOS 8. Nothing works!
What have I missed?
I have created a bare project with the following view controller to illustrate the problem:
#import EventKit;
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *resultLabel;
#property (nonatomic, assign) NSUInteger calendarCount;
#property (nonatomic, strong) EKEventStore *eventStore;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.resultLabel.text = #"";
self.eventStore = [[EKEventStore alloc] init];
}
- (IBAction)userDidTapCreateCalendar:(id)sender {
EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
if (status == EKAuthorizationStatusNotDetermined) {
__weak typeof(self) weakSelf = self;
[self.eventStore requestAccessToEntityType:EKEntityTypeEvent
completion:^(BOOL granted, NSError *error) {
if (granted) {
[weakSelf createCalendar];
} else {
weakSelf.resultLabel.text = #"If you don't grant me access, I've got no hope!";
}
}];
} else if (status == EKAuthorizationStatusAuthorized) {
[self createCalendar];
} else {
self.resultLabel.text = #"Access denied previously, go fix it in Settings.";
}
}
- (void)createCalendar
{
EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityMaskEvent eventStore:self.eventStore];
calendar.title = [NSString stringWithFormat:#"Calendar %0lu", (unsigned long)++self.calendarCount];
[self.eventStore.sources enumerateObjectsUsingBlock:^(EKSource *source, NSUInteger idx, BOOL *stop) {
if (source.sourceType == EKSourceTypeLocal) {
calendar.source = source;
*stop = YES;
}
}];
NSError *error = nil;
BOOL success = [self.eventStore saveCalendar:calendar commit:YES error:&error];
if (success && error == nil) {
self.resultLabel.text = [NSString stringWithFormat:#"Created \"Calendar %0lu\" with id %#",
(unsigned long)self.calendarCount, calendar.calendarIdentifier];
} else {
self.resultLabel.text = [NSString stringWithFormat:#"Error: %#", error];
}
}
#end
I've tested this code on a device, and it works OK (change EKEntityMaskEvent -> EKEntityTypeEvent). I can see new calendar in iOS calendars.
Looks like simulator does not actually saves your calendars. I've got the same issue some time ago.
Related
I've found very limited resources on this topic (CMPedometer). I was wondering if anyone here has managed to get this to work properly. My code is fairly simple, and has more than what I'm trying to do. Basically, the step counter does not increment EVERY step a user takes.
It actually is tracking every step the user takes but it updates so slowly and I can't figure out why. I even tried using NSTimer to make a request to update the labels every half a second. I want to try to get the step counter to update as a user takes a step. Here is my code...
#import "ViewController.h"
#import <CoreMotion/CoreMotion.h>
#interface ViewController ()
#property (nonatomic, strong) CMPedometer *pedometer;
#property (nonatomic, weak) IBOutlet UILabel *startDateLabel;
#property (nonatomic, weak) IBOutlet UILabel *endDateLabel;
#property (nonatomic, weak) IBOutlet UILabel *stepsLabel;
#property (nonatomic, weak) IBOutlet UILabel *distanceLabel;
#property (nonatomic, weak) IBOutlet UILabel *ascendedLabel;
#property (nonatomic, weak) IBOutlet UILabel *descendedLabel;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if ([CMPedometer isStepCountingAvailable]) {
self.pedometer = [[CMPedometer alloc] init];
[NSTimer scheduledTimerWithTimeInterval:0.5f
target:self
selector:#selector(recursiveQuery)
userInfo:nil
repeats:YES];
} else {
NSLog(#"Nothing available");
self.startDateLabel.text = #"";
self.endDateLabel.text = #"";
self.stepsLabel.text = #"";
self.distanceLabel.text = #"";
self.ascendedLabel.text = #"";
self.descendedLabel.text = #"";
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.pedometer startPedometerUpdatesFromDate:[NSDate date]
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"data:%#, error:%#", pedometerData, error);
});
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.pedometer stopPedometerUpdates];
}
- (NSString *)stringWithObject:(id)obj {
return [NSString stringWithFormat:#"%#", obj];
}
- (NSString *)stringForDate:(NSDate *)date {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
return [formatter stringFromDate:date];
}
- (void)queryDataFrom:(NSDate *)startDate toDate:(NSDate *)endDate {
[self.pedometer queryPedometerDataFromDate:startDate
toDate:endDate
withHandler:
^(CMPedometerData *pedometerData, NSError *error) {
NSLog(#"data:%#, error:%#", pedometerData, error);
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Error = %#",error.userInfo);
self.startDateLabel.text = #"";
self.endDateLabel.text = #"";
self.stepsLabel.text = #"";
self.distanceLabel.text = #"";
self.ascendedLabel.text = #"";
self.descendedLabel.text = #"";
} else {
self.startDateLabel.text = [self stringForDate:pedometerData.startDate];
self.endDateLabel.text = [self stringForDate:pedometerData.endDate];
self.stepsLabel.text = [self stringWithObject:pedometerData.numberOfSteps];
self.distanceLabel.text = [NSString stringWithFormat:#"%.1f[m]", [pedometerData.distance floatValue]];
self.ascendedLabel.text = [self stringWithObject:pedometerData.floorsAscended];
self.descendedLabel.text = [self stringWithObject:pedometerData.floorsDescended];
}
});
}];
}
- (void)recursiveQuery {
NSDate *to = [NSDate date];
NSDate *from = [to dateByAddingTimeInterval:-(24. * 3600.)];
[self queryDataFrom:from toDate:to];
}
Thanks in advance for any feedback!
EDIT
It seems the appropriate method to use for live updates is the following..
- (void)liveSteps {
[self.pedometer startPedometerUpdatesFromDate:[NSDate date]
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Steps %#",pedometerData.numberOfSteps);
});
}];
}
However, even this is severely delayed. Does anyone have any idea how to use this properly to essentially update as the user takes a step?
I can only confirm your findings. I also wanted to get "true" realtime information. As it seems at this point, the API is not capable of this; even by forcing the updates into a queue, sync, async, etc.
For references and others with this question, here is the code I use based on Swift 3 and Xcode 8.2. I simply apply this portion of code in the concerned viewcontroller, after checking the CMPedometer.isStepCountingAvailable().
As you can see, I've included a small animation to update the UILabel in a more fluid manner.
// Steps update in near realtime - UILabel
self.pedoMeter.startUpdates(from: midnightOfToday) { (data: CMPedometerData?, error) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
if(error == nil){
self.todaySteps.text = "\(data!.numberOfSteps)"
// Animate the changes of numbers in the UILabel
UILabel.transition(with: self.todaySteps,
duration: 0.50,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
}
})
}
I have a ContentPageViewController class, it has the IBOutlet stuff. I write my getter of ContentPageViewController in the ViewController like the following code.
ContentPageViewController.h
#interface ContentPageViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *busName;
#property (weak, nonatomic) IBOutlet UILabel *busTime;
#property (weak, nonatomic) IBOutlet UILabel *busType;
#end
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// instantiation from a storyboard
ContentPageViewController *page = [self.storyboard instantiateViewControllerWithIdentifier:#"ContentPageViewController"];
self.page = page;
// send url request
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://api.apb-shuttle.info/now" ]];
[self sendURLRequest:request];
// add the view of ContendPageViewController into ViewController
[self.view addSubview:self.page.view];
}
// It works if i remove the following code
- (ContentPageVC *)page
{
if (_page) _page = [[ContentPageViewController alloc] init];
return _page;
}
Nothing happened when I updated it. And it gave me a nil.
- (void)updateUI
{
// I got null here
NSLog("%#", self.page.busName)
// The spacing style font
NSDictionary *titleAttributes = #{
NSKernAttributeName: #10.0f
};
NSDictionary *attributes = #{
NSKernAttributeName: #5.0f
};
self.page.busName.attributedText = [[NSMutableAttributedString alloc] initWithString:bus.name
attributes:titleAttributes];
self.page.busTime.attributedText = [[NSMutableAttributedString alloc] initWithString:bus.depart
attributes:titleAttributes];
self.page.busType.attributedText = [[NSMutableAttributedString alloc] initWithString:bus.note
attributes:attributes];
}
The following code is when I called the updateUI:
- (void)sendURLRequest:(NSURLRequest *)requestObj
{
isLoading = YES;
[RequestHandler PerformRequestHandler:requestObj withCompletionHandler:^(NSDictionary *data, NSError *error) {
if (!error) {
bus = [JSONParser JSON2Bus:data];
// Add the bus object into the array.
[self.busArray addObject: bus];
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
[self updateUI];
isLoading = NO;
}];
} else {
NSLog(#"%#", [error localizedDescription]);
}
}];
}
But it worked if I removed the getter above.
I have no idea how it works, please give me some hint. Thanks.
Check your IBOutlet is connected.
Check the method you are calling isn't called before the view is created from the storyboard/nib
EDIT
The lines of code that you added, are overriding your getter. And every time you call self.page, your creating a new instance!
// It works if i remove the following code
- (ContentPageVC *)page
{
if (_page) _page = [[ContentPageViewController alloc] init];
return _page;
}
It should be like so:
// It works if i remove the following code
- (ContentPageVC *)page
{
if (!_page) _page = [[ContentPageViewController alloc] init]; // Added the ! mark, only if nil you would create a new instance.
return _page;
}
Plus you are calling alloc init on it, so Its not the same instance from storyboard!
So you should do this:
- (ContentPageVC *)page
{
if (!_page) _page = [self.storyboard instantiateViewControllerWithIdentifier:#"ContentPageViewController"];
return _page;
}
And remove this lines of code:
// instantiation from a storyboard
ContentPageViewController *page = [self.storyboard instantiateViewControllerWithIdentifier:#"ContentPageViewController"];
self.page = page;
Every time you call "self.page" the override getter function will call. and return the same instance.
I've found very limited resources on this topic (CMPedometer). I was wondering if anyone here has managed to get this to work properly. My code is fairly simple, and has more than what I'm trying to do. Basically, the step counter does not increment EVERY step a user takes.
It actually is tracking every step the user takes but it updates so slowly and I can't figure out why. I even tried using NSTimer to make a request to update the labels every half a second. I want to try to get the step counter to update as a user takes a step. Here is my code...
#import "ViewController.h"
#import <CoreMotion/CoreMotion.h>
#interface ViewController ()
#property (nonatomic, strong) CMPedometer *pedometer;
#property (nonatomic, weak) IBOutlet UILabel *startDateLabel;
#property (nonatomic, weak) IBOutlet UILabel *endDateLabel;
#property (nonatomic, weak) IBOutlet UILabel *stepsLabel;
#property (nonatomic, weak) IBOutlet UILabel *distanceLabel;
#property (nonatomic, weak) IBOutlet UILabel *ascendedLabel;
#property (nonatomic, weak) IBOutlet UILabel *descendedLabel;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if ([CMPedometer isStepCountingAvailable]) {
self.pedometer = [[CMPedometer alloc] init];
[NSTimer scheduledTimerWithTimeInterval:0.5f
target:self
selector:#selector(recursiveQuery)
userInfo:nil
repeats:YES];
} else {
NSLog(#"Nothing available");
self.startDateLabel.text = #"";
self.endDateLabel.text = #"";
self.stepsLabel.text = #"";
self.distanceLabel.text = #"";
self.ascendedLabel.text = #"";
self.descendedLabel.text = #"";
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.pedometer startPedometerUpdatesFromDate:[NSDate date]
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"data:%#, error:%#", pedometerData, error);
});
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.pedometer stopPedometerUpdates];
}
- (NSString *)stringWithObject:(id)obj {
return [NSString stringWithFormat:#"%#", obj];
}
- (NSString *)stringForDate:(NSDate *)date {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
return [formatter stringFromDate:date];
}
- (void)queryDataFrom:(NSDate *)startDate toDate:(NSDate *)endDate {
[self.pedometer queryPedometerDataFromDate:startDate
toDate:endDate
withHandler:
^(CMPedometerData *pedometerData, NSError *error) {
NSLog(#"data:%#, error:%#", pedometerData, error);
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Error = %#",error.userInfo);
self.startDateLabel.text = #"";
self.endDateLabel.text = #"";
self.stepsLabel.text = #"";
self.distanceLabel.text = #"";
self.ascendedLabel.text = #"";
self.descendedLabel.text = #"";
} else {
self.startDateLabel.text = [self stringForDate:pedometerData.startDate];
self.endDateLabel.text = [self stringForDate:pedometerData.endDate];
self.stepsLabel.text = [self stringWithObject:pedometerData.numberOfSteps];
self.distanceLabel.text = [NSString stringWithFormat:#"%.1f[m]", [pedometerData.distance floatValue]];
self.ascendedLabel.text = [self stringWithObject:pedometerData.floorsAscended];
self.descendedLabel.text = [self stringWithObject:pedometerData.floorsDescended];
}
});
}];
}
- (void)recursiveQuery {
NSDate *to = [NSDate date];
NSDate *from = [to dateByAddingTimeInterval:-(24. * 3600.)];
[self queryDataFrom:from toDate:to];
}
Thanks in advance for any feedback!
EDIT
It seems the appropriate method to use for live updates is the following..
- (void)liveSteps {
[self.pedometer startPedometerUpdatesFromDate:[NSDate date]
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Steps %#",pedometerData.numberOfSteps);
});
}];
}
However, even this is severely delayed. Does anyone have any idea how to use this properly to essentially update as the user takes a step?
I can only confirm your findings. I also wanted to get "true" realtime information. As it seems at this point, the API is not capable of this; even by forcing the updates into a queue, sync, async, etc.
For references and others with this question, here is the code I use based on Swift 3 and Xcode 8.2. I simply apply this portion of code in the concerned viewcontroller, after checking the CMPedometer.isStepCountingAvailable().
As you can see, I've included a small animation to update the UILabel in a more fluid manner.
// Steps update in near realtime - UILabel
self.pedoMeter.startUpdates(from: midnightOfToday) { (data: CMPedometerData?, error) -> Void in
DispatchQueue.main.async(execute: { () -> Void in
if(error == nil){
self.todaySteps.text = "\(data!.numberOfSteps)"
// Animate the changes of numbers in the UILabel
UILabel.transition(with: self.todaySteps,
duration: 0.50,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
}
})
}
I need to update position in core data base. When I add object or remove it from that base everything works fine. Problem occurs when I want to update object in that base. The problem is that program throws me:
Thread 1: EXC_BAD_ACCES(code = 2, adres = 0x38).
It's weird because it throws this error but updtades object at core data. I'm using simulator on Xcode 5. I've tried to comment line by line back in code but it hasn't helped anymore. Taking look at the address of the error and the screen shot there is no address compatible with each other. What is wrong?
Code is this:
[self insertPositionRightAfterChangeValues:name rating:[changedRate stringValue] urlMain:[newInsert urlMain] contentUrl:[newInsert contentUrl] coverUrl:[newInsert coverUrl] date:[newInsert date] type:[newInsert type] int:integer];
And the rest of this method:
-(void)insertPositionRightAfterChangeValues:(NSString *)name rating:(NSString *)rating urlMain:(NSString *)urlMain contentUrl:(NSString *)contentUrl coverUrl:(NSString *)coverUrl date:(NSString *)date type:(NSString *)type int:(int)position
{
int motherPosision = position;
//insert new one
NSManagedObjectContext *context2 = [self managedObjectContext];
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:context2];
newEntry.name = [[NSString alloc] initWithString: name];
newEntry.rating = [[NSString alloc] initWithString: rating];
newEntry.urlMain = [[NSString alloc] initWithString: urlMain];
newEntry.contentUrl = [[NSString alloc] initWithString: contentUrl];
newEntry.coverUrl = [[NSString alloc] initWithString: coverUrl];
newEntry.date = [[NSString alloc] initWithString: date];
newEntry.type = [[NSString alloc] initWithString: type];
NSLog(#"%#", newEntry.name);
NSLog(#"%#", newEntry.rating);
NSLog(#"%#", newEntry.urlMain);
NSLog(#"%#", newEntry.contentUrl);
NSLog(#"%#", newEntry.coverUrl);
NSLog(#"%#", newEntry.date);
NSLog(#"%#", newEntry.type);
NSError *error2;
if (![context2 save:&error2]) {
NSLog(#"Whoops, couldn't save: %#", [error2 localizedDescription]);
}
else{
NSLog(#"SAVED!!!");
}
}
And the screenshot with this error:
UPDATE:
When I comment line where I invoke method to insert new object I get following error: CoreData: error: Failed to call designated initializer on NSManagedObject class 'Kwejki'
UPDATE 2:
#interface Kwejki : NSManagedObject<NSCopying>
#property (nonatomic, strong) NSString * type;
#property (nonatomic, strong) NSString * contentUrl;
#property (nonatomic, strong) NSString * coverUrl;
#property (nonatomic, strong) NSString * rating;
#property (nonatomic, strong) NSString * urlMain;
#property (nonatomic, strong) NSString * date;
#property (nonatomic, strong) NSString * name;
#end
#implementation Kwejki
#synthesize name;
#synthesize date;
#synthesize urlMain;
#synthesize rating;
#synthesize coverUrl;
#synthesize contentUrl;
#synthesize type;
-(Kwejki *)copyWithZone:(NSZone *)zone
{
Kwejki *copyModel = [[Kwejki allocWithZone:zone] init];
if(copyModel)
{
copyModel.name = [self name];
copyModel.date = [self date];
copyModel.urlMain = [self urlMain];
copyModel.rating = [self rating];
copyModel.coverUrl = [self coverUrl];
copyModel.contentUrl = [self contentUrl];
copyModel.type = [self type];
}
return copyModel;
}
#end
-(void)addNewPosition:(ScrollViewViewController *)ScrollController recentlyDownloadedItem:(KwejkModel *)modelTmp
{
if(modelTmp == nil)
{
NSLog(#"YES NULL");
}
else
{
NSLog(#"Not null");
}
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:self.managedObjectContext];
NSLog(#"%#", [modelTmp getNameOfItem]);
newEntry.name = [[NSString alloc] initWithString:[modelTmp getNameOfItem]];
newEntry.rating = [modelTmp getRateOfPosition];
newEntry.urlMain = [modelTmp getUrlAdress];
newEntry.contentUrl = [modelTmp getContentUrl];
newEntry.coverUrl = [modelTmp getCoverImage];
newEntry.date = [modelTmp getDateOfItem];
if([modelTmp getType] == photo)
{
newEntry.type = #"0";
}
else if([modelTmp getType] == gif)
{
newEntry.type = #"1";
}
else if ([modelTmp getType] == video)
{
newEntry.type = #"2";
}
NSLog(#"%#", newEntry.name);
NSLog(#"%#", newEntry.rating);
NSLog(#"%#", newEntry.urlMain);
NSLog(#"%#", newEntry.contentUrl);
NSLog(#"%#", newEntry.coverUrl);
NSLog(#"%#", newEntry.date);
NSLog(#"%#", newEntry.type);
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
else{
NSLog(#"UDALO SIE!!!");
}
[modelArray insertObject:newEntry atIndex:0];
[self.tableView reloadData];
}
I've tried to overcome this problem just adding new object again as update but it also doesn't helped. How I add new object: I tap long on position, new window opens and there I add new values, invoke from that view method from Main ViewController, and here it crashes. When I add normally object as described above but in separate window everything works. I really don't know what is wrong.
When I see this error it normally mean that I did not setup my NSManageObject correctly.
Things to check:
Add a break point in your insertPositionRightAfterChangeValues method and step through to find where the exception is thrown. (My guess is when your creating your Kwejki object.)
That Kwejki is a subclass of NSManageObject.
That Kwejki entity and class name are correct. (Could you show your .xcdatamodeId entity properties for Kwejki?)
If everything is correct, then for an insanity check I would create the Kwejki object the long way.
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([Kwejki class]) inManagedObjectContext:context2];
Kwejki* newEntry = [[Kwejki alloc] initWithEntity:entity insertIntoManagedObjectContext:context2];
Then you can see if the entity is a problem or your Kwejki is.
P.S. As a helpful suggestion instead of typing in the entity name.
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:context2];
I would do something like this.
Kwejki * newEntry = [NSEntityDescription entityForName:NSStringFromClass([Kwejki class]) inManagedObjectContext:context2];
This give you a compiler check for entity name.
#import "CPPedometerViewController.h"
#import <CoreMotion/CoreMotion.h>
#interface CPPedometerViewController ()
#property (weak, nonatomic) IBOutlet UILabel *stepsCountingLabel;
#property (nonatomic, strong) CMStepCounter *cmStepCounter;
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#property (nonatomic, strong) NSMutableArray *stepsArray;
#end
#implementation CPPedometerViewController
- (NSOperationQueue *)operationQueue
{
if (_operationQueue == nil)
{
_operationQueue = [NSOperationQueue new];
}
return _operationQueue;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self QueryExistingStep];
NSLog( #"steps array = %#", _stepsArray);
}
-(void)QueryExistingStep
{
//get todays date
NSDate *now = [NSDate date];
// get six days ago from today
NSDate *sixDaysAgo = [now dateByAddingTimeInterval:-6*24*60*60];
//array to hold step values
_stepsArray = [[NSMutableArray alloc] initWithCapacity:7];
//check if step counting is avaliable
if ([CMStepCounter isStepCountingAvailable])
{
//init step counter
self.cmStepCounter = [[CMStepCounter alloc] init];
//get seven days before from date & to date.
for (NSDate *toDate = [sixDaysAgo copy]; [toDate compare: now] <= 0;
toDate = [toDate dateByAddingTimeInterval:24 * 60 * 60] ) {
//get day before
NSDate *fromDate = [[toDate copy] dateByAddingTimeInterval: -1 * 24 * 60 * 60];
[self.cmStepCounter queryStepCountStartingFrom:fromDate to:toDate toQueue:self.operationQueue withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (!error) {
NSLog(#"queryStepCount returned %ld steps", (long)numberOfSteps);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateArrayWithStepCounter:numberOfSteps];
}];
} else {
NSLog(#"Error occured: %#", error.localizedDescription);
}
}];
}
} else {
// stuffhappens
}
}
- (void)updateArrayWithStepCounter:(NSInteger)numberOfSteps {
[_stepsArray addObject:[NSNumber numberWithInteger:numberOfSteps]];
}
#end
I'm looking to have an array full of steps from the past seven days, and then insert them into to a NSinteger for each day. e.g. NSinteger daySeven = 242, NSInteger daySix = 823 ... etc too today.
However the array seems to clear after exiting the updateArrayWithStepCounter method. Any idea on how i could fix this so each number of steps goes into separate NSIntegers also. Thanks, Ryan.
EDIT:
Here is the NSLog output:
2014-01-25 22:51:36.314 Project[6633:60b] steps array = (
)
2014-01-25 22:51:36.332 Project[6633:420f] queryStepCount returned 3505 steps
2014-01-25 22:51:36.334 Project[6633:420f] queryStepCount returned 3365 steps
2014-01-25 22:51:36.335 Project[6633:420f] queryStepCount returned 7206 steps
2014-01-25 22:51:36.337 Project[6633:420f] queryStepCount returned 6045 steps
2014-01-25 22:51:36.339 Project[6633:420f] queryStepCount returned 5259 steps
2014-01-25 22:51:36.342 Project[6633:420f] queryStepCount returned 6723 steps
2014-01-25 22:51:36.344 Project[6633:420f] queryStepCount returned 440 steps
Here is the output shown as suggested. As you can see its definitely getting the values however when it checks the array after running the method its now empty.
Could i be adding it to the array incorrectly?
I hope this is clearer I'm stumped. Thanks
First, what's the output of this?
NSLog(#"steps array = %#", _stepsArray);
When someone ask you something, you should try to reply with the exact information requested, just saying "it says that array is empty" doesn't help, because maybe someone can see something that you don't see in the output.
Said this, I would add some more NSLog around, because it could be that your handler is not called, or not called with the information that you expect.
Use the following, and let us know the output :)
[self.cmStepCounter queryStepCountStartingFrom:fromDate to:toDate toQueue:self.operationQueue withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (!error) {
NSLog(#"queryStepCount returned %d steps", numberOfSteps);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateArrayWithStepCounter:numberOfSteps];
}];
} else {
NSLog(#"Error occured: %#", error.localizedDescription);
}
}];
EDIT: From the new posted output of the NSLog, I can understand the problem. The fact is that the handler runs asynchronously, it means that you can't just output the array on viewDidLoad, because it runs BEFORE the array received all the values, so you should refactor your code to trigger a method when all the data is ready.
Here a revision of your code that is more readable (removed some useless "copy" call, updated your "for conditions", etc...), now it should be really easy to understand what's going on, and how to perform additional logic.
#import "PYViewController.h"
#import <CoreMotion/CoreMotion.h>
#interface PYViewController ()
#property (weak, nonatomic) IBOutlet UILabel *stepsCountingLabel;
#property (nonatomic, strong) CMStepCounter *cmStepCounter;
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#property (nonatomic, strong) NSMutableArray *stepsArray;
#end
#implementation PYViewController
- (NSOperationQueue *)operationQueue {
if (_operationQueue == nil) {
_operationQueue = [NSOperationQueue new];
_operationQueue.maxConcurrentOperationCount = 1; // process 1 operation at a time, or we could end with unexpected results on _stepsArray
}
return _operationQueue;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self queryExistingStep];
}
-(void)queryExistingStep {
// Get now date
NSDate *now = [NSDate date];
// Array to hold step values
_stepsArray = [[NSMutableArray alloc] initWithCapacity:7];
// Check if step counting is avaliable
if ([CMStepCounter isStepCountingAvailable]) {
// Init step counter
self.cmStepCounter = [[CMStepCounter alloc] init];
// Tweak this value as you need (you can also parametrize it)
NSInteger daysBack = 6;
for (NSInteger day = daysBack; day > 0; day--) {
NSDate *fromDate = [now dateByAddingTimeInterval: -day * 24 * 60 * 60];
NSDate *toDate = [fromDate dateByAddingTimeInterval:24 * 60 * 60];
[self.cmStepCounter queryStepCountStartingFrom:fromDate to:toDate toQueue:self.operationQueue withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (!error) {
NSLog(#"queryStepCount returned %ld steps", (long)numberOfSteps);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[_stepsArray addObject:#(numberOfSteps)];
if ( day == 1) { // Just reached the last element, do what you want with the data
NSLog(#"_stepsArray filled with data: %#", _stepsArray);
// [self updateMyUI];
}
}];
} else {
NSLog(#"Error occured: %#", error.localizedDescription);
}
}];
}
} else {
NSLog(#"device not supported");
}
}
#end
What i would do is waiting for the array to get all the needed value and do stuff later. It works for me.
- (void)queryPast6DayStepCounts
{
NSLog(#"queryPast6DayStepCounts visited!");
self.past6DaysDailySteps = [[NSMutableArray alloc] initWithCapacity:6];
// past 6 days
for (NSInteger day = 6; day >= 1; day--)
{
NSDate *fromDate = [self.todayMidnight dateByAddingTimeInterval:-day * 24 * 60 * 60];
NSDate *toDate = [fromDate dateByAddingTimeInterval:24 * 60 * 60];
[self.cmStepCounter
queryStepCountStartingFrom:fromDate to:toDate
toQueue:[NSOperationQueue mainQueue]
withHandler:^(NSInteger numberOfSteps, NSError *error)
{
if (!error)
{
[self.past6DaysDailySteps addObject:#(numberOfSteps)];
if (self.past6DaysDailySteps.count == 6) {
[self viewDidFinishQueryPast6DaysData];
}
}
else
{
NSLog(#"Error occured: %#", error.localizedDescription);
}
}
];
}
}
-(void)viewDidFinishQueryPast6DaysData
{
NSLog(#"queryPast6DayStepCounts finish! %#", self.past6DaysDailySteps);
// do other things
}