I'm trying to replicate a fitness app similar to Runtastic's Fitness Apps.
Sit-Ups
This our first app that uses the phone’s built-in accelerometer to detect movement. You need to hold the phone against your chest then sit up quickly enough and high enough for the accelerometer to register the movement and the app to count 1 sit-up. Be sure to do a proper sit-up by going high enough!
I did a prototype app similar to this question here and tried to implement a way to count sit-ups.
- (void)viewDidLoad {
[super viewDidLoad];
int count = 0;
motionManager = [[CMMotionManager alloc]init];
if (motionManager.deviceMotionAvailable)
{
motionManager.deviceMotionUpdateInterval = 0.1;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
// Get the attitude of the device
CMAttitude *attitude = motion.attitude;
// Get the pitch (in radians) and convert to degrees.
double degree = attitude.pitch * 180.0/M_PI;
NSLog(#"%f", degree);
dispatch_async(dispatch_get_main_queue(), ^{
// Update some UI
if (degree >=75.0)
{
//it keeps counting if the condition is true!
count++;
self.lblCount.text = [NSString stringWithFormat:#"%i", count];
}
});
}];
NSLog(#"Device motion started");
}
else
{
NSLog(#"Device motion unavailable");
}
}
The if condition statement works, as if I place the device on my chest and do a proper sit-up, but the problem about this if statement is that it will just continue counting and I would want it to only count when the device has gone back to it's original position.
Can anyone come up with a logical implementation for this?
A simple boolean flag did the trick:
__block BOOL situp = NO;
if (!situp)
{
if (degree >=75.0)
{
count++;
self.lblCount.text = [NSString stringWithFormat:#"%i", count];
situp = YES;
}
}
else
{
if (degree <=10.0)
{
situp = NO;
}
}
Not the best logical implementation here, but it gets the job done...
Related
I’ve not had much experience with semaphores, nor with blocks. I’ve seen various suggestions for how to turn an asynchronous call into a synchronous one. In this case I just want to wait to be sure the lens of the iPhone has changed focus before I snap another picture.
I’ve added a completion block (with a little routine to prove that I’m seeing it). But how to block the rest of my code (running on the main thread) until I get the completion callback?
- (void) changeFocusSettings
{
if ([SettingsController settings].useFocusSweep)
{
// increment the focus setting
float tmp = [SettingsController settings].fsLensPosition;
float fstmp =[[SettingsController settings] nextLensPosition: [SettingsController settings].fsLensPosition]; // get next lensposition
[SettingsController settings].fsLensPosition = fstmp ;
tmp = [SettingsController settings].fsLensPosition;
if ([self.captureDevice lockForConfiguration: nil] == YES)
{
__weak typeof(self) weakSelf = self;
[self.captureDevice setFocusModeLockedWithLensPosition:tmp
completionHandler:^(CMTime syncTime) {
NSLog(#"focus over..time = %f", CMTimeGetSeconds(syncTime));
[weakSelf focusCompletionHandler : syncTime];
}];
}
}
}
- (bool) focusCompletionHandler : (CMTime)syncTime
{
NSLog(#"focus done, time = %f", CMTimeGetSeconds(syncTime));
return true;
}
changeFocusSettings is called from another routine entirely. I image some kind of semaphore set just inside changeFocusSettings and then the focuscompletionHandler resets it. But the details are beyond me.
Thank you.
I worked through it myself, it wasn't hard at all and it appears to be working. Here's the code in case it helps someone else. And if you happen to spot an error, please let me know.
dispatch_semaphore_t focusSemaphore;
...
- (bool) focusCompletionHandler : (CMTime)syncTime
{
dispatch_semaphore_signal(focusSemaphore);
return true;
}
- (void) changeFocusSettings
{
focusSemaphore = dispatch_semaphore_create(0); // create semaphone to wait for focuschange to complete
if ([SettingsController settings].useFocusSweep)
{
// increment the fsLensposition
float tmp = [SettingsController settings].fsLensPosition;
float fstmp =[[SettingsController settings] nextLensPosition: [SettingsController settings].fsLensPosition]; // get next lensposition
[SettingsController settings].fsLensPosition = fstmp ;
tmp = [SettingsController settings].fsLensPosition;
NSLog(#"focus setting = %f and = %f", tmp, fstmp);
if ([self.captureDevice lockForConfiguration: nil] == YES)
{
__weak typeof(self) weakSelf = self;
[self.captureDevice setFocusModeLockedWithLensPosition:tmp
completionHandler:^(CMTime syncTime) {
[weakSelf focusCompletionHandler : syncTime];
}];
dispatch_semaphore_wait(focusSemaphore, DISPATCH_TIME_FOREVER);
}
}
}
Is there a way to get the estimated time of On Demand Resources download?
I'd like to show an alert until they are all downloaded.
[alertDownload showCustom:self image:[UIImage imageNamed:#"icon.jpg"]
color:[UIColor blueColor]
title:#"Download..."
subTitle:#"Download in progress"
closeButtonTitle:nil
duration: ODR ETA];
Right now I have
if (request1.progress.fractionCompleted < 1) {
// code above
}
but the alert will not automatically disappear when the download is completed, it will look at the duration of the alert.
OK, if you can get the fraction complete value and you can measure time, then you know how long you have left.
When you start the download, record the start time in an instance variable:
#interface MyClass () {
NSTimeInterval _downloadStartTime;
}
- (void)startDownload
{
...
_downloadStartTime = [NSDate timeIntervalSinceReferenceDate];
...
}
and then in your notification handler, where you receive the fraction complete, use:
double fractionComplete = 0.2; // For example
if (fractionComplete > 0.0) { // Avoid divide-by-zero
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsed = now - _downloadStartTime;
double timeLeft = (elapsedTime / fractionComplete) * (1.0 - fractionComplete);
}
Note: I have not tackled your displaying of the alert dialog and I don't think the logic you are using will work (you don't want to display a new alert every time you get an update). I am avoiding this whole area and concentrating on the ETA logic only.
So, also thanks to the help of #trojanfoe, I achieved this way.
Basically, I'm not setting the alert duration when creating the alert, but I'm updating it depending on the download progress.
Until the download finished, I'm repeatedly setting the duration to 20.0f .
Then, when the download completed, I'm setting the duration to 1.0f (so the alert will disappear in 1 second).
NSTimeInterval _alertDuration;
- (void)viewDidLoad {
[request1 conditionallyBeginAccessingResourcesWithCompletionHandler:^
(BOOL resourcesAvailable)
{
if (resourcesAvailable) {
// use it
} else {
[request1 beginAccessingResourcesWithCompletionHandler:^
(NSError * _Nullable error)
{
if (error == nil) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
[alertDownload showCustom:self image:[UIImage
imageNamed:#"icon.jpg"]
color:[UIColor blueColor]
title:#"Download..."
subTitle:#"Download in progress"
closeButtonTitle:nil
duration:_alertDuration];
}
];
} else {
// handle error
}
}];
}
}];
.
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary *)change
context:(nullable void *)context {
if((object == request1.progress) && [keyPath
isEqualToString:#"fractionCompleted"]) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
if(request1.progress.fractionCompleted == 1) {
_alertDuration = 1.0f;
} else {
_alertDuration = 20.0f;
}
}];
}
}
After seeing this question, I tried to code up a quick program that would save the watches accelerometer and gyroscope data to a file.
#implementation InterfaceController{
NSMutableArray *accData;
bool recording;
}
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
self.motionManager = [[CMMotionManager alloc] init];
[self.motionManager setAccelerometerUpdateInterval:.01];
}
- (IBAction)startStopRecording {
if (!recording){//We are starting to record.
recording = YES;
accData = [[NSMutableArray alloc] init];
[self.startRecording setTitle:#"Stop Recording"];
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
[accData addObject:[NSString stringWithFormat:#"%f, %f, %f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]];
}];
}else{
recording = NO;//we are stopping the recording
[self.motionManager stopAccelerometerUpdates];
[self.startRecording setTitle:#"Start Recording"];
[InterfaceController openParentApplication:#{ #"accData": accData } reply:^(NSDictionary *replyInfo, NSError *error) { //this method saves the array to a csv file.
NSLog(#"Data has been saved.");
}];
}
}
I had plotted this data and for the life of me, no matter how hard I shook the watch, all my plots looked like this:
Until 8 hours later, I started to suspect that I wasn't grabbing the acceleration data from the watch, but rather from the phone (sitting still on the table next to me). I ran some tests and confirmed that this is exactly what is happening.
Which leads me to the original question. How do I pull acceleration/gyro/data from the watch and not from the iPhone?
The problem was that I wasn't running watchOS2. I assumed I was but it's still in beta and I hadn't installed it. The data I was getting was accelerometer data from the phone. Also, currently, you can only get acc data from the watch using watchOS2 and not gyro data.
you can use CoreMotion framework to get activity data.
while I can only get accel data, the gyro often return false.
I have the following setup:
An iPhone lies with the display to the ceiling on a table (alpha = 0 degrees). When the iPhone is moved upwards like shown in the image above the alpha angle increases.
How do I compute the value of the alpha angle without taking care of any other axes which could change. I am only interested in this one axis.
How do I get the correct alpha angle the iPhone has when lifting up from the table? How do I get notified when the value of alpha changes?
You can use the CMMotionManager class to monitor device motion changes.
Objective C
// Ensure to keep a strong reference to the motion manager otherwise you won't get updates
self.motionManager = [[CMMotionManager alloc] init];
if (self.motionManager.deviceMotionAvailable) {
self.motionManager.deviceMotionUpdateInterval = 0.1;
// For use in the montionManager's handler to prevent strong reference cycle
__weak typeof(self) weakSelf = self;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.motionManager startDeviceMotionUpdatesToQueue:queue
withHandler:^(CMDeviceMotion *motion, NSError *error) {
// Get the attitude of the device
CMAttitude *attitude = motion.attitude;
// Get the pitch (in radians) and convert to degrees.
NSLog(#"%f", attitude.pitch * 180.0/M_PI);
dispatch_async(dispatch_get_main_queue(), ^{
// Update some UI
});
}];
NSLog(#"Device motion started");
}else {
NSLog(#"Device motion unavailable");
}
Swift
// Ensure to keep a strong reference to the motion manager otherwise you won't get updates
self.motionManager = CMMotionManager()
if motionManager?.deviceMotionAvailable == true {
motionManager?.deviceMotionUpdateInterval = 0.1
let queue = OperationQueue()
motionManager?.startDeviceMotionUpdatesToQueue(queue, withHandler: { [weak self] motion, error in
// Get the attitude of the device
if let attitude = motion?.attitude {
// Get the pitch (in radians) and convert to degrees.
print(attitude.pitch * 180.0/Double.pi)
DispatchQueue.main.async {
// Update some UI
}
}
})
print("Device motion started")
}else {
print("Device motion unavailable")
}
NSHipster is (as always) a great source of information, and the article on CMDeviceMotion is no exception.
Swift 4:
let motionManager = CMMotionManager()
if motionManager.isDeviceMotionAvailable {
motionManager.deviceMotionUpdateInterval = 0.1
motionManager.startDeviceMotionUpdates(to: OperationQueue()) { [weak self] (motion, error) -> Void in
if let attitude = motion?.attitude {
print(attitude.pitch * 180 / Double.pi)
DispatchQueue.main.async{
// Update UI
}
}
}
print("Device motion started")
}
else {
print("Device motion unavailable")
}
I am adding achievements into an xCode project. The code below that I am using works fine in awarding the achievement but the only problem is that it is constantly being awarded in the background in the debug console. This is happening every time I load the game.
I also find that when the achievement is awarded for the very first time the completion banner is on repeat.
My question today is how do I edit the code to only award the achievement once, display the banner and then never appear again?
-(void)Scoring
{
ScoreNumber = ScoreNumber + AddedScore;
AddedScore = AddedScore - 1;
if (AddedScore < 0) {
AddedScore = 0;
}
Score.text = [NSString stringWithFormat:#"%i", ScoreNumber];
if (ScoreNumber > 110 && ScoreNumber < 1000) {
LevelNUmber = 2;
//self.view.backgroundColor = [UIColor greenColor];
GKAchievement *achievement= [[GKAchievement alloc] initWithIdentifier:#"_level1easy"];
achievement.percentComplete = 100.0;
achievement.showsCompletionBanner = YES;
if(achievement!= NULL)
{
NSArray *achievements = [NSArray arrayWithObjects:achievement, nil];
[GKAchievement reportAchievements:achievements withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error in reporting achievements: %#", error);
} else {
NSLog(#"Achievement 1 Success");
}
}];
}
}
If I understand your question correctly, then you just need to save the state of your achievement somewhere, then check it when you go into your Scoring method. Maybe try saving in NSUserdefaults, for example;
NSUserDefaults *savedScoring = [NSUserDefaults standardUserDefaults];
[savedScoring setObject:self.showsCompletionBanner forKey:#"showsCompletionBanner"];
Then check this whenever you game loads.