How to replace UIAccelerometer with CMMotionManager? - ios

I'm new to iOS development.
I follow the tutorial from Ray Wenderlich to create a little location based AR app. However, the tutorial uses an AR Toolkit which has not been updated for a while. The UIAccelerometer it uses has already been deprecated since iOS 5, so when I try to run it on my iPhone (iOS 7.0.4), the Xcode says that there are 3 warnings, and all of them are caused by UIAccelerometer.
The result it leads to is that all the marks stay at the center of the screen one above another, and the tilt does not work at all.
According to my research, I guess what I need to do is to use CMMotionManager instead of UIAccelerometer, but as I said before, I'm totally new to iOS development and have no idea how to replace it.
Here is the source code. I add some little functions such that you can manually add locations that are not in the Google database, but I don't think it is these functions that result in the problem.
Thanks for you help in advance!

Try this link: https://www.inkling.com/read/learning-ios-programming-alasdair-allan-2nd/chapter-9/the-core-motion-framework
I'm learning a few tidbits that translate some-what with the UIAccelerometer
i.e.
[self setAccelometerManager [UIAccelerometer sharedAccelerometer]];
could become
[self.motionManager = [[CMMotionManager alloc] init];
Setting manual update intervals like
[[self accelerometerManager] setUpdateInterval: 0.25];
you can have
self.motionManager.accelerometerUpdateInterval = 0.25;
and releasing the delegate
self.accelerometerManager.delegate = nil;
would now be
[self.motionManager stopDeviceMotionUpdates];
Also from the link, I ended up doing something like this:
motionManager = [[CMMotionManager alloc] init];
motionManager.accelerometerUpdateInterval = 1.0/10.0; // Update at 10Hz
if (motionManager.accelerometerAvailable) {
queue = [NSOperationQueue currentQueue];
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
switch (currentOrientation) {
case UIDeviceOrientationLandscapeLeft:
viewAngle = atan2(accelerometerData.acceleration.x, accelerometerData.acceleration.z);
break;
case UIDeviceOrientationLandscapeRight:
viewAngle = atan2(-accelerometerData.acceleration.x, accelerometerData.acceleration.z);
break;
case UIDeviceOrientationPortrait:
viewAngle = atan2(accelerometerData.acceleration.y, accelerometerData.acceleration.z);
break;
case UIDeviceOrientationPortraitUpsideDown:
viewAngle = atan2(-accelerometerData.acceleration.y, accelerometerData.acceleration.z);
break;
default:
break;
}
[self updateCenterCoordinate];
}];
}

Related

How to make my CMMotionManager available through a singleton?

So I have been designing an iOS game (in Sprite Kit) for a while now and just recently added a CMMotionManager to my project so that my character would be controlled by the tilt of the screen. It took some fiddling but I got it to work, and here's how I've implemented it:
In my initWithSize method I have
self.motionManager = [[CMMotionManager alloc] init];
self.referenceAttitude = nil;
Then, I've written methods beginMotionSensing and switchToLiveSensing. The first is intended to get an idea of how the phone is being held and the second starts the game itself. Using SKActions, I call the first method, wait a second, and call the second. They look something like this:
-(void)beginMotionSensing{
CMDeviceMotion *deviceMotion = self.motionManager.deviceMotion;
CMAttitude *attitude = deviceMotion.attitude;
self.referenceAttitude = attitude;
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
[self setNull:motion];
}];
self.motionManager.deviceMotionUpdateInterval = .3;
}
and,
-(void)switchToLiveSensing{
gameIsLive = YES;
[self.motionManager stopDeviceMotionUpdates];
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
[self captureRoll:motion];
}];
self.motionManager.deviceMotionUpdateInterval = .02;
}
So I had all of this set up and it was working great, until I added the ability to play the game more than once. Before this, I would simply have to close out of the app and quit the game when I lost. Obviously this became annoying so I added a "game over" scene, with the ability to go back and play the game scene again. The game scene is presented again like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SKTransition *transition = [SKTransition fadeWithColor:[UIColor colorWithRed:147.0/255.0 green:213.0/255.0 blue:216.0/255.0 alpha:1.0] duration:2.0f];
MyScene *gameScene = [MyScene alloc];
gameScene = [gameScene initWithSize:self.frame.size passedInFuel:100];
[self.view presentScene:gameScene transition:transition];
}
I had remembered someone saying something in a previous post about a singleton - the comment was directed at someone implementing a CMMotionManager. They had said something along the lines of, "you should probably make it accessible through a singleton if you are going to initialize your class more than once." Well, sure enough, that's what I'm doing now. I had forgotten about this until I was playing the game and noticed that suddenly the phone seemed to ignore my tilt and went along with it's own business, killing my character in the process. It hasn't happened since, but is clearly something that needs to be fixed.
Could someone help me implement this so-called "singleton"?
I'm not convinced this is the problem you had, as the CMMotionManager object should be released anyway when your SKScene goes away. Can you duplicate the problem by starting the game again quickly after it ends?
You could probably do it by adding a class method in your main view/navigation controller (which ever one stays around longest) to create a singleton and then make it available to your SKScene object.
e.g.
+ (CMMotionManager *)sharedInstance
{
static CMMotionManager *sharedCMMM = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCMMM = [[CMMotionManager alloc] init];
// Do any other initialisation stuff here
});
return sharedCMMM;
}

CMMotionManager vs UIAccelerometer efficiency

I've been working on an AR framework for a while now and am trying to update from UIAccelerometer (deprecated) to CMMotionManager but am running into some efficiency problems?
Basically it seems like CMMotionManager is MUCH larger and slower than UIAccelerometer is. Has anyone experienced performance issues with CMMotionManager before?
As you can see here, I had this:
accelerometer = [UIAccelerometer sharedAccelerometer];
accelerometer.updateInterval = 0.01;
[accelerometer setDelegate:self];
and
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
rollingZ = (acceleration.z * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));
rollingX = (acceleration.y * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
if (rollingZ > 0.0) currentInclination = inc_avg(atan(rollingX / rollingZ) + M_PI / 2.0);
else if (rollingZ < 0.0) currentInclination = inc_avg(atan(rollingX / rollingZ) - M_PI / 2.0);
else if (rollingX < 0) currentInclination = inc_avg(M_PI/2.0);
else if (rollingX >= 0) currentInclination = inc_avg(3 * M_PI/2.0);
}
and all works great even on "older" devices like the iPhone 4 (not really old but yea...).
But when trying the exact same code but with CMMotionManager:
motionManager = [[CMMotionManager alloc] init];
with
[motionManager setAccelerometerUpdateInterval:0.01];
[motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error){
rollingZ = (accelerometerData.acceleration.z * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));
rollingX = (accelerometerData.acceleration.y * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
if (rollingZ > 0.0) currentInclination = inc_avg(atan(rollingX / rollingZ) + M_PI / 2.0);
else if (rollingZ < 0.0) currentInclination = inc_avg(atan(rollingX / rollingZ) - M_PI / 2.0);
else if (rollingX < 0) currentInclination = inc_avg(M_PI/2.0);
else if (rollingX >= 0) currentInclination = inc_avg(3 * M_PI/2.0);
}];
The math seems to slow the crap out of it..! I say this because when I remove all the math part it works great.
An iPhone 5 will work alright but an iPhone 4S will show signs of lag and the iPhone 4 will just freeze...
(I can give you more details if you want but its relatively complicated to explain)
I was just having this same problem, and wouldn't you know it, the solution was in the documentation ;)
The problem is with the block format. All of the tutorials seem to favor that method, but Apple recommends periodic polling of the CMMotionManager as a more performance oriented approach. The block format adds overhead.
From the CMMotionManager Class Reference:
To handle motion data by periodic sampling, the app calls a “start”
method taking no arguments and periodically accesses the motion data
held by a property for a given type of motion data. This approach is
the recommended approach for apps such as games. Handling
accelerometer data in a block introduces additional overhead, and most
game apps are interested only the latest sample of motion data when
they render a frame.
So what you want to do, from the docs again:
Call startAccelerometerUpdates to begin updates and periodically
access CMAccelerometerData objects by reading the accelerometerData
property.
Something along these lines
CMMotionManager *mManager = [(AppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager];
[mManager startAccelerometerUpdates];
Then, in some sort of periodically updating method of your choosing:
CMMotionManager *mManager = [(SEPAppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager];
CMAccelerometerData *aData = mManager.accelerometerData;
This solution appears to work as well as UIAccelerometer on an iPhone 4 from the limited testing I've done.
I use CADisplayLink.
First, setup CMMotionManager instance.
-(void)viewDidLoad
{
[super viewDidLoad];
self.motionManager = [[CMMotionManager alloc]init];
if(self.motionManager.isDeviceMotionAvailable)
{
[self.motionManager startDeviceMotionUpdates];
}
[self setupDisplayLink];
}
Secondly setup displaylink instance like this:
-(void)setupDisplayLink
{
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(update:)];
displayLink.frameInterval = 10;// how many frames to skip before next update
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
Display link is an object that is connected to your device screen, and which can run specified selector with specified frame rate; More on them here
Thirdly you should implement update: method you specified as display link selector.
-(void)update:(CADisplayLink *)displayLink
{
// handle new accelerometer data here
CMDeviceMotion *deviceMotion = self.motionManager.deviceMotion;
NSLog(#"Acceleration: %#", deviceMotion.accelerometerData.acceleration);
}

How to detect when the phone has been put down

I would like an action to take place when the phone is stationary for 2 seconds. I've searched for ages around google and stack overflow. I discovered that "Accelerometer DidAccelerate" has been depreciated and that CoreMotion is the replacement. Everything I have seen has been to do with the 'shaking' motion. I've tried reading through apple's documentation but It just confuses me!
Basically, I want the app to detect that the g-forces on the phone have remained within a small limit for a certain amount of time (suggesting that the phone has been laid down on a table or something) and for it to call and instance or make the app do something.
Any help would be greatly appreciated.
It's similar to the problem described in Simple iPhone motion detect. The basic setup for CMMotionManager is described in the Apple docs like Mike Pollard stated in his comment. I recommend especially the Handling Processed Device Motion Data section.
What you then need is CMDeviceMotion.userAcceleration which contains the pure acceleration without gravity.
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
// UPDATE: set interval to 0.02 sec
motionManager.deviceMotionUpdateInterval = 1.0 / 50.0;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *deviceMotion, NSError *error) {
CMAcceleration userAcceleration = deviceMotion.userAcceleration;
double totalAcceleration = sqrt(userAcceleration.x * userAcceleration.x +
userAcceleration.y * userAcceleration.y + userAcceleration.z * userAcceleration.z);
// UPDATE: print debug information
NSLog (#"total=%f x=%f y=%f z=%f", totalAcceleration, userAcceleration.x, userAcceleration.y, userAcceleration.z);
// if(totalAcceleration < SOME_LIMIT) ...
Then proceed like codeplasma has described in his answer above.
Also be aware that the solution might not be precise if used in the underground, bus, etc. because of external accelerations.
You can do something like this:
CMMotionManager *mManager = [[CMMotionManager alloc] init];
if ([mManager isAccelerometerAvailable] == YES) {
__block float lastActivityBefore = 0.0;
[mManager setAccelerometerUpdateInterval:0.1];
[mManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
double totalAcceleration = sqrt(accelerometerData.acceleration.x * accelerometerData.acceleration.x + accelerometerData.acceleration.y * accelerometerData.acceleration.y + accelerometerData.acceleration.z * accelerometerData.acceleration.z);
if(totalAcceleration < SOME_LIMIT)
lastActivityBefore = lastActivityBefore + 0.1;
else
lastActivityBefore = 0.0;
if(lastActivityBefore >= 2.0)
{
//do something
}
}];
}
Accelerometer will show some minimal acceleration even if your device is steady, so you should make a testing in order to determine SOME_LIMIT value.
Also be advised that you should have only one instance CMMotionManager class in your app, so you're better to put it in your AppDelegate and initialize it only once.

CMMotionManager with multitasking

I'm using CMMotionManager in my app so I can get the device motion info. I have these two methods:
- (void)startDeviceMotion {
motionManager = [[CMMotionManager alloc] init];
motionManager.showsDeviceMovementDisplay = YES;
motionManager.deviceMotionUpdateInterval = 1.0 / 120.0;
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical];
}
Second is:
- (void)stopDeviceMotion {
[motionManager stopDeviceMotionUpdates];
[motionManager release];
motionManager = nil;
}
They're launched when the app starts and when the app finishes respectively. My problem is now multitasking. If I get my problem into background and then I bring it to foreground again, I get a message (with NSZombie activated) telling me that a [CMMotionManager retain] message is being sent to a deallocated instance.
Where could my problem be?
Thanks!
Try using Jonathan's suggestion here. Basically, to make sure only one instance of your CMMotionManager is created, put your motionManager in AppDelegate and retrieve it by this method wherever you want to use your motionManager.
-(CMMotionManager *)motionManager
{
CMMotionManager *motionManager = nil;
id appDelegate = [UIApplication sharedApplication].delegate;
if ([appDelegate respondsToSelector:#selector(motionManager)]) {
motionManager = [appDelegate motionManager];
}
return motionManager;
}
Let me know if this works for you.

Is there a better way to do this? Want to use a loop, but it breaks [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 11 years ago.
I have a the following chunk of code that's adding up to six Player objects to six Seat UIView objects. I'm adding them using data stored in an NSMuteableDictionary.
Here's how it looks at the moment:
MoneyCentralAppDelegate *delegate = (MoneyCentralAppDelegate *) [[UIApplication sharedApplication] delegate];
//add player1
NSMutableDictionary *player1Dictionary = [[delegate appDataDictionary] valueForKey:#"Player1"];
Player *player1 = [[Player alloc] initWithFrame:CGRectMake(0,0,0,0)];
player1.playerName = [player1Dictionary valueForKey#"PlayerName"];
player1.avatar = [UIImage imageNamed:[player1Dictionary valueForKey:#"Avatar"]];
[seat1 setAlpha:1];
[seat1 addSubView:player1];
//add player2
NSMutableDictionary *player2Dictionary = [[delegate appDataDictionary] valueForKey:#"Player2"];
if ([player2Dictionary valueForKey:#"PlayerName"] != #"Enter your name") {
Player *player2 = [[Player alloc] initWithFrame:CGRectMake(0,0,0,0)];
player2.playerName = [player2Dictionary valueForKey#"PlayerName"];
player2.avatar = [UIImage imageNamed:[player2Dictionary valueForKey:#"Avatar"]];
[seat2 setAlpha:1];
[seat2 addSubView:player2];
}
//And so on for another 4 more players...
Isn't there a way to streamline this code by using a loop? I've tried the following but it doesn't work:
for (int i=1; i=6, i++) {
[self addPlayerToBoard:i];
}
- (void) addPlayerToBoard:(int)playerNumber {
MoneyCentralAppDelegate *delegate = (MoneyCentralAppDelegate *) [[UIApplication sharedApplication] delegate];
NSMutableDictionary *playerDictionary;
NSString *thePlayer;
Seat *seat;
switch (playerNumber) {
case 1:
thePlayer = #"Player1";
seat = seat1;
break;
case 2:
thePlayer = #"Player2";
seat = seat2:
break;
case 3:
//and so on to 6
}
playerDictionary = [[delegate appDataDictionary] valueForKey:thePlayer];
if ([playerDictionary valueForKey:#"PlayerName"] != #"Enter your name") {
Player *newPlayer = [[Player alloc] initWithFrame:CGRectMake(0,0,0,0)];
newPlayer.playerName = [playerDictionary valueForKey:#"PlayerName"];
newPlayer.avatar = [UIImage imageName:[playerDictionary valueForKey:#"Avatar"]];
[seat setAlpha:1];
[seat addSubView:newPlayer];
}
}
As far as I can see this code is good. It compiles with no errors or warnings but when I try to start a new game the app just freezes when it attempts to run that code.
Also just as a side question, the if statement where it checks if the value of PlayerName != #"Enter your name" apparently evaluates to true not matter what because I end up with six players every time. Is that the wrong way to check the value of a string in an nsmutabledictionary?
I know this is a lot to look at but I'd really appreciate any help or suggestions here.
Thanks a ton.
The problem is the way you created your for loop. The proper syntax is: for(initialize; test; update). To iterate over the variable i from 1 to 6, you should use:
for(int i = 1; i <= 6; i++) {
[self addPlayerToBoard:i];
}
When you use the != or == operators on an object, you are comparing the object's pointer. This means that two objects will only be the same if they are exactly the same object, not just equivalent. To determine if two objects are the same, you should use [object1 isEqualTo:object2]. If you know an object will be an NSString, you should use [string1 isEqualToString:string2] instead, as it will be faster.
if (![[playerDictionary valueForKey:#"PlayerName"] isEqualToString:#"Enter your name"]) {
...
}

Resources