Alpinista
Alpinista

Reputation: 3741

How to determine the direction of a iPhone shake

I am using the accelerometer to scroll multiple subViews in a UIScrollVIew. I want the view (portrait orientation) to scroll to the right when the user flicks the iPhone to the right, and scroll to the left when the device is flicked to the left.

I thought I could do that just by noting positive or negative x acceleration values, but I see that the values are usually a mixture of positive and negative values. I have set the floor at 1.5g to eliminate non-shake movement, and am looking at the x values over the duration of .5 seconds.

I'm sure there is a trigonometrical method for determining the overall direction of a flick, and that you have to measure values over the duration of the flick motion. I'm also sure that someone has already figured this one out.

Any ideas out there?

Thanks

Upvotes: 4

Views: 3366

Answers (2)

Jon - LBAB
Jon - LBAB

Reputation: 938

I developed a solution which gives me better feedback than the proposed solution (only for left and right shake). The way I did it here is quite sensitive (recognizes a small shake), but sensitivity can be adapted by changing tresholdFirstMove and tresholdBackMove (increase for lower sensitivity)

In Swift : (in your viewController. And add "import CoreMotion")

var startedLeftTilt = false
var startedRightTilt = false
var dateLastShake = NSDate(timeIntervalSinceNow: -2)
var dateStartedTilt = NSDate(timeIntervalSinceNow: -2)
var motionManager = CMMotionManager()
let tresholdFirstMove = 3.0
let tresholdBackMove = 0.5

override func viewDidLoad() {
    // your code

    motionManager.gyroUpdateInterval = 0.01
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    motionManager.startGyroUpdatesToQueue(NSOperationQueue.currentQueue(), withHandler: { (gyroData, error) -> Void in
        self.handleGyroData(gyroData.rotationRate)
    })

}

private func handleGyroData(rotation: CMRotationRate) {

    if fabs(rotation.z) > tresholdFirstMove && fabs(dateLastShake.timeIntervalSinceNow) > 0.3
    {
        if !startedRightTilt && !startedLeftTilt
        {
            dateStartedTilt = NSDate()
            if (rotation.z > 0)
            {
                startedLeftTilt = true
                startedRightTilt = false
            }
            else
            {
                startedRightTilt = true
                startedLeftTilt = false
            }
        }
    }

    if fabs(dateStartedTilt.timeIntervalSinceNow) >= 0.3
    {
        startedRightTilt = false
        startedLeftTilt = false
    }
    else
    {
        if (fabs(rotation.z) > tresholdBackMove)
        {
            if startedLeftTilt && rotation.z < 0
            {
                dateLastShake = NSDate()
                startedRightTilt = false
                startedLeftTilt = false

                println("\\\n Shaked left\n/")
            }
            else if startedRightTilt && rotation.z > 0
            {
                dateLastShake = NSDate()
                startedRightTilt = false
                startedLeftTilt = false

                println("\\\n Shaked right\n/")
            }
        }
    }

}

Upvotes: 8

Alpinista
Alpinista

Reputation: 3741

OK, worked out a solution. When I detect a shake motion (acceleration greater than 1.5 on the x axis), I start a timer and set a BOOL to true. While the BOOL is true I add acceleration values. When the timer expires, I stop adding acceleration values and determine direction of the shake by the sign of the total acceleration.


- (void)accelerometer:(UIAccelerometer *)acel didAccelerate:(UIAcceleration *)aceler {

    if (fabsf(aceler.x) > 1.5)
    {
        shake = YES;
        NSTimeInterval myInterval = .75;
        [NSTimer scheduledTimerWithTimeInterval:myInterval target:self selector:@selector(endShake) userInfo:nil repeats:NO];
        return;
    }

    if(shake)
    {
        totalG += aceler.x;
    }
}

- (void) endShake {
    shake = NO;
    int direction;
    if (totalG isLessThan 0) direction = 1;
    if(totalG isGreaterThan 0) direction = -1;
    [self changePageByShake:direction];
    totalG = 0;
}

Note: I couldn't get the < and > symbols to format correctly in the codeblock above, so I substituted isLessThan and isGreaterThan for the symbols.

Upvotes: 5

Related Questions