jerky motion using CMMotionManager iPhone

1.2k views Asked by At

I am using CMMotionManager to move buttons, images etc. on my view as the device is tipped forward, backward, right, left. I get the attitude pitch and roll and then use these to assign values to a string representing pixel placement on the screen.

Here is how I get the info

motionManager = [[CMMotionManager alloc] init];
                   motionManager.deviceMotionUpdateInterval = 0.05f;
                   [motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
                       NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
                       [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
                       [formatter setMaximumFractionDigits:2];
                       [formatter setRoundingMode: NSNumberFormatterRoundUp];
                       numberString = [formatter stringFromNumber:[NSNumber numberWithFloat:motion.attitude.roll /8]];
                       numberString1 = [formatter stringFromNumber:[NSNumber numberWithFloat:motion.attitude.pitch /8]];
                   }];

and this is how I assign them.

NSString *deltax = numberString;
    NSString *deltay = numberString1;
    NSLog(@"xString;%@",numberString);
    if ([deltax isEqualToString:@"-0.01"]) {
        xString = @"160";}
    else if ([deltax isEqualToString:@"-0.02"]) {
        xString = @"161";}
    else if ([deltax isEqualToString:@"-0.03"]) {
        xString = @"162";}
    else if ([deltax isEqualToString:@"-0.04"]) {
        xString = @"163";}

etc.

I have made them so the x axis continuously goes up and down (back and forth) on the screen. i.e. -.01 is the same as -.40, .01 and .40. and they are approached the same as you go up from -.01 and .01the values get bigger and from -.40 and 4.4 they get smaller. I did the same with y axis also. -.01 and .01 are 284 and -.20 and .20 are at 264. However in this case the numbers go up from -.01 and down from .01.

here are some examples

//x axis

if ([deltax isEqualToString:@"-0.01"]) {
        xString = @"160";}
    else if ([deltax isEqualToString:@"-0.02"]) {
        xString = @"161";}
    else if ([deltax isEqualToString:@"-0.03"]) {
        xString = @"162";}
    else if ([deltax isEqualToString:@"-0.04"]) {
        xString = @"163";}
    else if ([deltax isEqualToString:@"-0.37"]) {
        xString = @"156";}
    else if ([deltax isEqualToString:@"-0.38"]) {
        xString = @"157";}
    else if ([deltax isEqualToString:@"-0.39"]) {
        xString = @"158";}
    else if ([deltax isEqualToString:@"-0.4"]) {
        xString = @"159";}
    else if ([deltax isEqualToString:@"0.01"]) {
        xString = @"159";}
    else if ([deltax isEqualToString:@"0.02"]) {
        xString = @"158";}
    else if ([deltax isEqualToString:@"0.03"]) {
        xString = @"157"; }
    else if ([deltax isEqualToString:@"0.04"]) {
        xString = @"156";}
    else if ([deltax isEqualToString:@"0.37"]) {
        xString = @"163";}
    else if ([deltax isEqualToString:@"0.38"]) {
        xString = @"162";}
    else if ([deltax isEqualToString:@"0.39"]) {
        xString = @"161";}
    else if ([deltax isEqualToString:@"0.4"]) {
        xString = @"160";}

//y axis




if ([deltay isEqualToString:@"-0.01"]) {
                yString = @"284";}
            else if ([deltay isEqualToString:@"-0.02"]) {
                yString = @"285";}
            else if ([deltay isEqualToString:@"-0.03"]) {
                yString = @"286";}
            else if ([deltay isEqualToString:@"-0.04"]) {
                yString = @"287";}
            else if ([deltay isEqualToString:@"-0.17"]) {
                yString = @"300"; }
            else if ([deltay isEqualToString:@"-0.18"]) {
                yString = @"301";}
            else if ([deltay isEqualToString:@"-0.19"]) {
                yString = @"302";}
            else if ([deltay isEqualToString:@"-0.2"]) {
                yString = @"303";}
            else if ([deltay isEqualToString:@"0.01"]) {
                yString = @"283";}
            else if ([deltay isEqualToString:@"0.02"]) {
                yString = @"282";}
            else if ([deltay isEqualToString:@"0.03"]) {
                yString = @"281";}
            else if ([deltay isEqualToString:@"0.04"]) {
                yString = @"280"; }
            else if ([deltay isEqualToString:@"0.17"]) {
                yString = @"267";}
            else if ([deltay isEqualToString:@"0.18"]) {
                yString = @"266";}
            else if ([deltay isEqualToString:@"0.19"]) {
                yString = @"265";}
            else if ([deltay isEqualToString:@"0.2"]) {
                yString = @"264";}

In this way there is continuous flow from one orientation to the next.

However I am having a small problem. when the phone is tipped forward and passes from .2 faceUp orientation to .2 faceDown orientation. I get a jerky motion. In faceUp .2 buttons etc. move to the left all of a sudden and when it moves to faceDown .20 they move to the right. Otherwise the buttons etc. are centered at .19 and -.19 and return there to operate normally (as expected). I have tried to call DeviceOrientationFaceUp and FaceDown but this didn't seem to help.

Does anyone have any experience with this behavior and know of a fix or have any suggestions.

I also add this part to move the buttons up and down and side to side. Using this method the button up and down side to side wherever the position of the phone.

//used to assign numerical values
NSString *string2 = xString;
int n = [string2 intValue];
NSLog(@"n:%d",n);
NSString *string3 = xString1;
int p = [string3 intValue];
NSLog(@"p:%d",p);
NSString *string4 = yString;
int q = [string4 intValue];
NSLog(@"q:%d",q);
NSString *string5 = yString1;
int r = [string5 intValue];
NSLog(@"r:%d",r);

//used to move the buttons etc.
   imageView2.center = CGPointMake(p, q);
            imageView.center = CGPointMake(n -63, q);
            imageframe.center = CGPointMake(n -63, q);
            textView.center = CGPointMake(n -62, q - 184);
            label1.center = CGPointMake(n  + 97, q  - 195 );
            english.center = CGPointMake(n + 97, q - 159);

this is a kind of parallax motion thing.

2

There are 2 answers

3
Rob On BEST ANSWER

A couple of reactions:

  1. I might suggest using gravity rather than attitude, as it nicely represents the physical orientation of the device.

  2. Rather than using a string to hold the value, I might just update the numeric values.

  3. Rather than all of those if statements, just have a formula that represents how the view should be placed.

  4. If you want to move a bunch of views, you should put them in a UIView (commonly called a "container view", not to be confused with the custom container control in IB of the same name) and then just move the container view.

So, if using autolayout, I might have the following properties:

@property (nonatomic, strong) CMMotionManager *motionManager;
@property CGFloat top;
@property CGFloat left;

And then I can update the autolayout constraints like so:

self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 0.05f;

self.top = self.topConstraint.constant;
self.left = self.leadingConstraint.constant;

[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
    self.leadingConstraint.constant = self.left - motion.gravity.x / 8.0 * 100.0;
    self.topConstraint.constant     = self.top  - motion.gravity.y / 8.0 * 100.0;
}];

Or if not using autolayout:

@property (nonatomic, strong) CMMotionManager *motionManager;
@property CGPoint originalCenter;

and

self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 0.05f;

self.originalCenter = self.containerView.center;

[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
    self.containerView.center = CGPointMake(self.originalCenter.x - motion.gravity.x / 8.0 * 100.0, self.originalCenter.y - motion.gravity.y / 8.0 * 100.0);
}];

Frankly, having the motion manager operate in the main queue gives me the willies, so I might (a) create a NSOperationQueue for the motion updates and only update some class property; and (b) use a CADisplayLink (part of the QuartzCore.framework) to update the view (if needed). Thus:

@property (nonatomic, strong) CMMotionManager *motionManager;
@property (nonatomic, strong) NSOperationQueue *motionQueue;

@property (nonatomic) CMAcceleration gravity;
@property (nonatomic) CGPoint originalCenter;
@property (nonatomic) BOOL updatePosition;

@property (nonatomic, strong) NSMutableArray *angles;
@property (nonatomic, strong) CADisplayLink *displayLink;

and

- (void)viewDidDisappear:(BOOL)animated
{
    [self stopDisplayLink];
    [self.motionManager stopDeviceMotionUpdates];
    self.motionManager = nil;
    self.motionQueue = nil;
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];    

    self.motionManager = [[CMMotionManager alloc] init];
    self.motionManager.deviceMotionUpdateInterval = 0.05f;

    self.motionQueue = [[NSOperationQueue alloc] init];
    self.motionQueue.name = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:@".motion"];

    self.originalCenter = self.containerView.center;
    self.updatePosition = NO;

    [self.motionManager startDeviceMotionUpdatesToQueue:self.motionQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
        @synchronized(self) {
            self.gravity = motion.gravity;
            self.updatePosition = YES;
        }
    }];

    [self startDisplayLink];
}

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    @synchronized(self) {
        if (!self.updatePosition)
            return;

        self.containerView.center = CGPointMake(self.originalCenter.x - self.gravity.x / 8.0 * 100.0, self.originalCenter.y - self.gravity.y / 8.0 * 100.0);

        self.updatePosition = NO;
    }
}
0
Minas Petterson On

I did the following: A math that takes the max and minimum values within a adjustable window (flicker noise) and takes the average. No more oscillations!!!

Here is my code to avoid flickering in may app:

float xAxis,yAxis, zAxis;
xAxis = self.manager.accelerometerData.acceleration.x;
yAxis = self.manager.accelerometerData.acceleration.y;
zAxis = self.manager.accelerometerData.acceleration.z;

// returns if the phone is lying on the table:
if (zAxis < -0.8 || zAxis > 0.8) return;

CGFloat angle =  atan2f(xAxis, yAxis ); // The angle!!!

float noise = 0.011; // Flicker noise (the noise you want to filter)

// max and min are global variables
if (angle > max){
    max = angle;
    min = max - noise;
}
if (angle < min){
    min = angle;
    max = min + noise;
}

// Average: (no flickering):
angle = min + (max - min) / 2.0;