CMDevice​Motion

Beneath the smooth glass of each shiny iPhone, nestled on a logic board between touch screen controllers and Apple-designed SoCs, the gyroscope and accelerometer sit largely neglected.
Need it be so? The Core Motion framework makes it surprisingly easy to harness these sensors, opening the door to user interactions above and beyond the tapping and swiping we do every day.
For devices that include the M7 or M8 motion processor, the Core Motion framework also provides access to stored motion activity, such as step counts, stairs climbed, and movement type (walking, cycling, etc.).

Core Motion allows a developer to observe and respond to the motion and orientation of an iOS device by inspecting the raw and processed data from a combination of built-in sensors, including the accelerometer, gyroscope, and magnetometer.
Both accelerometer and gyroscope data are presented in terms of three axes that run through the iOS device. For an iPhone held in portrait orientation, the X-axis runs through the device from left (negative values) to right (positive values), the Y-axis through the device from bottom (-) to top (+), and the Z-axis runs perpendicularly through the screen from the back (-) to the front (+).
The composited device motion data are presented in a few different ways, each with their own uses, as we’ll see below.


Device X-, Y-, and Z-axes

CMMotionManager
The CMMotionManager
class provides access to all the motion data on an iOS device. Interestingly, Core Motion provides both “pull” and “push” access to motion data. To “pull” motion data, you can access the current status of any sensor or the composited data as read-only properties ofCMMotionManager
. To receive “pushed” data, you start the collection of your desired data with a block or closure that receives updates at a specified interval.
To keep performance at the highest level, Apple recommends using a single shared CMMotionManager
instance throughout your app.
CMMotionManager
provides a consistent interface for each of the four motion data types:

accelerometer, gyro, magnetometer, and deviceMotion.

As an example, here are the ways you can interact with the gyroscope—simply replace gyro
with the motion data type you need.

Checking for Availability

let manager = CMMotionManager()
if manager.gyroAvailable { 
      // ...
}

To make things simpler and equivalent between Swift and Objective-C, assume we’ve declared a manager
instance as a view controller property for all the examples to come.

Setting the Update Interval

manager.gyroUpdateInterval = 0.1

This is an NSTimeInterval, so specify your update time in seconds: lower for smoother responsiveness, higher for less CPU usage.

Starting Updates to “pull” Data

 manager.startGyroUpdates()

After this call, manager.gyroData
is accessible at any time with the device’s current gyroscope data.

let queue = NSOperationQueue.mainQueuemanager.startGyroUpdatesToQueue(queue) { 
  (data, error) in // ...
}

The handler closure will be called at the frequency given by the update interval.

Stopping Updates

manager.stopGyroUpdates()

Using the Accelerometer##

Let’s say we want to give the splash page of our app a fun effect, with the background image staying level no matter how the phone is tilted.
Consider the following code:
First, we check to make sure our device makes accelerometer data available, next we specify a very high update rate, and then we begin updates to a closure that will rotate a UIImageView
property:

 if manager.accelerometerAvailable { 
    manager.accelerometerUpdateInterval = 0.01 
    manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMAccelerometerData!, error: NSError!) in 
       let rotation = atan2(data.acceleration.x, data.acceleration.y) - M_PI self?.imageView.transform = CGAffineTransformMakeRotation(CGFloat(rotation)) 
    }
}

Each packet of CMAccelerometerData
includes an x,y, and z value—each of these shows the amount of acceleration in Gs (where G is one unit of gravity) for that axis. That is, if your device were stationary and straight up in portrait orientation, it would have acceleration (0, -1, 0); laying flat on its back on the table would be (0, 0, -1)
; and tilted forty-five degrees to the right would be something like (0.707, -0.707, 0).

We’re calculating the rotation by computing the arctan2
of the x and y
components from the accelerometer data and then using that rotation in a CGAffineTransform
. Our image should stay right-side up no matter how the phone is turned—here it is in a hypothetical app for the National Air & Space Museum (my favorite museum as a kid):

Rotation with accelerometer

The results are not terribly satisfactory—the image movement is jittery, and moving the device in space affects the accelerometer as much as or even more than rotating. These issues could be mitigated by sampling multiple readings and averaging them together, but instead let’s look at what happens when we involve the gyroscope.

Adding the Gyroscope##

Rather than use the raw gyroscope data that we would get with startGyroUpdates..., let’s get composited gyroscope and accelerometer data from the deviceMotion data type. Using the gyroscope, Core Motion separates user movement from gravitational acceleration and presents each as its own property of the CMDeviceMotion instance that we receive in our handler. The code is very similar to our first example:

UIClunkController##

We can also use the other, non-gravity portion of this composited gyro/acceleration data to add new methods of interaction. In this case, let’s use the userAcceleration property of CMDeviceMotion to navigate backward whenever a user taps the left side of her device against her hand.

Remember that the X-axis runs laterally through the device in our hand, with negative values to the left. If we sense a user acceleration to the left of more than 2.5 Gs, that will be the cue to pop our view controller from the stack. The implementation is only a couple lines different from our previous example:

if manager.deviceMotionAvailable {
   manager.deviceMotionUpdateInterval = 0.02
   manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) {  [weak self] (data: CMDeviceMotion!, error: NSError!) in 
         if data.userAcceleration.x < -2.5 { 
              self?.navigationController?.popViewControllerAnimated(true)
         }
   }
}

And it works like a charm—tapping the device in a detail view immediately takes us back to the list of exhibits:


Clunk to go back

Getting an Attitude#

Better acceleration data isn’t the only thing we gain by including gyroscope data—we now also know the device’s true orientation in space. We find this data in the attitude property of CMDeviceMotion, an instance of CMAttitude
. CMAttitude contains three different representations of the device’s orientation: Euler angles, a quaternion, and a rotation matrix. Each of these is in relation to a given reference frame.

Finding a Frame of Reference###

You can think of a reference frame as the resting orientation of the device from which an attitude is calculated. All four possible reference frames describe the device laying flat on a table, with increasing specificity about the direction it’s pointing.

CMAttitudeReferenceFrameXArbitraryZVertical
describes a device laying flat (vertical Z-axis) with an “arbitrary” X-axis. In practice, the X-axis is fixed to the orientation of the device when you first start device motion updates.

CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
is essentially the same but uses the magnetometer to correct possible variation in the gyroscope’s measurement over time. Using the magnetometer adds a CPU (and therefore battery) cost.

CMAttitudeReferenceFrameXMagneticNorthZVertical
describes a device laying flat, with its X-axis (i.e., the right side of the device) pointed toward magnetic north. This setting may require your user to perform that figure-eight motion with their device to calibrate the magnetometer.

CMAttitudeReferenceFrameXTrueNorthZVertical
is the same as the last, but this adjusts for the magnetic/true north discrepancy and therefore requires location data in addition to the magnetometer.

For our purposes, the default “arbitrary” reference frame will be fine - you’ll see why in a moment.

Euler Angles##

Of the three attitude representations, Euler angles are the most readily understood, as they simply describe rotation around each of the axes we’ve already been working with. pitch is rotation around the X-axis, increasing as the device tilts toward you, decreasing as it tilts away; roll is rotation around the Y-axis, decreasing as the device rotates to the left, increasing to the right; and yaw is rotation around the (vertical) Z-axis, decreasing clockwise, increasing counter-clockwise.

Each of these values follows what’s called the “right hand rule”: make a cupped hand with your thumb pointing up and point your thumb in the direction of any of the three axes. Turns that move toward your fingertips are positive, turns away are negative.

Keep It To Yourself##

Lastly, let’s try using the device’s attitude to enable a new interaction for a flash-card app, designed to be used by two study buddies. Instead of manually switching between the prompt and the answer, we’ll automatically switch the view as the device turns around, so the quizzer sees the answer while the person being quizzed only sees the prompt.
Figuring out this switch from the reference frame would be tricky. To know which angles to monitor, we would somehow need to take into account the starting orientation of the device and then determine which direction the device is pointing. Instead, we can save a CMAttitude instance and use it as the “zero point” for an adjusted set of Euler angles, calling the multiplyByInverseOfAttitude() method to translate all future attitude updates.
When the quizzer taps the button to begin the quiz, we first configure the interaction—note the “pull” of the deviceMotion for initialAttitude:

// get magnitude of vector via Pythagorean theorem
func  magnitudeFromAttitude(attitude: CMAttitude) -> Double { 
    return sqrt(pow(attitude.roll, 2) + pow(attitude.yaw, 2) + pow(attitude.pitch, 2))
}

// initial configurationvar
initialAttitude = manager.deviceMotion.attitude
var showingPrompt = false

// trigger values - a gap so there isn't a flicker zone
let showPromptTrigger = 1.0
let showAnswerTrigger = 0.8

Then, in our now familiar call to startDeviceMotionUpdates
, we calculate the magnitude of the vector described by the three Euler angles and use that as a trigger to show or hide the prompt view:

if manager.deviceMotionAvailable { 
    
   manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMDeviceMotion!, error: NSError!) in 

        // translate the attitude 
        data.attitude.multiplyByInverseOfAttitude(initialAttitude)
 
        // calculate magnitude of the change from our initial attitude
        let magnitude = magnitudeFromAttitude(data.attitude) ?? 0

         // show the prompt 
         if !showingPrompt && magnitude > showPromptTrigger { 
            if let promptViewController = self?.storyboard?.instantiateViewControllerWithIdentifier("PromptViewController") as? PromptViewController { 
               showingPrompt = true promptViewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve 
          self!.presentViewController(promptViewController, animated: true, completion: nil) 
            }
         }

         // hide the prompt 
        if showingPrompt && magnitude < showAnswerTrigger { 
           showingPrompt = false 
           self?.dismissViewControllerAnimated(true, completion: nil) 
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,380评论 0 23
  • 黑夜总是暗藏玄机,稍有缝隙便会暗流涌动显露端倪。在这样的沉静里,放空了的心脏犹如一座空城,白日里蓄谋已久的情绪...
    魏文晶阅读 347评论 0 2
  • 轻盈这个词是属于五月的,风和树叶在对话:没有什么,放不下。
    antigone阅读 184评论 0 2