原文连接:http://nshipster.com/cmdevicemotion/闭包
陀螺仪和加速器通常都难入咱们法眼。ide
Core Motion framework
使咱们能够很容易驾驭这些传感器,让用户动动手指点划滑就能够完成相关的交互。性能
含M7或M8处理器中的动态处理器的设备还特别提供了获取已保存动态活动的功能,就好比走了多少步,爬楼梯,还有另一些运动状态(走圈圈等)。测试
CM让开发者能够经过传感器(加速器、陀螺仪还有磁力仪)发送原始或经处理过的混合数据来观察并响应iOS设备朝向。动画
加速器和陀螺仪数据以iOS设备上3维坐标来展现。当手机呈水平放置的时候(以下图),X轴从左(负值)到右(正值),Y周从下(负值)到上(正值),还有就是Z轴垂直方向上从背屏(负值)到屏幕(正值)。spa
CoreMotionManager
类提供iOS设备动态数据的存取方法。有意思的是,CM提供了 "拉"和"推"两种方式的获取方式。 你能够经过获取当前任何传感器的状态或是CoreMotionManager
的只读属性组合数据来“拉”动态数据。你能够经过block闭包在特定的时间间隔来获取你想要的更新数据集合来捕获“推”数据。线程
为了保证在高层表现上的性能,苹果推荐你在APP中使用单例CoreMotionManager
。翻译
CoreMotionManager
为4类数据(传感器,加速器,陀螺仪,磁力仪)提供了一致的接口。举个栗子,与陀螺仪交互 - 获取你想要的数据。code
let manager = CoreMotionManager() if manager.gyroAvailable { // ... }
栗子中manager是视图控制器的一个属性orm
manager.gyroUpdateInterval = 0.1
时间单位用的是NSTimeInterval
,反应灵敏下降了,可是CPU使用率减小了。
manager.startGyroUpdates()
经过调用上述方法,manager.gyroData
就能够获取到设备当前的陀螺仪数据。
let queue = NSOperationQueue.mainQueue manager.startGyroUpdatesToQueue(queue) { (data, error) in // ... }
句柄闭包在给定的时间间隔会被不断的调用。
manager.stopGyroUpdates()
举个栗子给APP增长一个有趣的功能,不论设备怎么倾斜,背景图始终指向重力方向。
思考以下代码:
首先咱们核实确认咱们设备能够获取加速器的数据,紧接着咱们设定一个高频更新率,而后咱们开始在回调闭包中处理图片得旋转角度:
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)) } }
每一个CMAccelerometerData
包中都包含了x,y,z值 - 每一个值都展现重力在该坐标轴加速量。也就是说,若是你的设备竖直方向精致放置,那么值将是 (0, -1, 0);在桌子上方面了值会是(0, 0, -1);右倾斜个35度将会是(0.707, -0.707, 0)。
咱们计算旋转角度是经过将加速器数据中的x,y份量进行arctan2的计算,而后在CGAffineTransform
中使用该角度。咱们图像将会一直保持在右边,不论咱们怎么转动手机。
戳这里动图地址。。
结果不尽人意,图片动画有点钝,空间移动设备的影响与转动影响比起来一样甚至更为厉害。咱们能够经过屡次读取并取它们的均值来抵消此负面影响,可是咱们仍是开看下当咱们引入陀螺仪的时候会发生什么。
咱们不从使用startGyroUpdates
获取陀螺仪的原始数据开始。。。咱们直接从deviceMotion
数据类型中获取加速器和陀螺仪的组合数据。CM将用户的运动与重力加速度两块分割开来而且提供了CMDeviceMotionData
实例变量作句柄。上述栗子的简单代码以下:
if manager.deviceMotionAvailable { manager.deviceMotionUpdateInterval = 0.01 manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMDeviceMotionData!, error: NSError!) in let rotation = atan2(data.gravity.x, data.gravity.y) - M_PI self?.imageView.transform = CGAffineTransformMakeRotation(CGFloat(rotation)) } }
这样看上去就好多了。。。原图戳这里。
咱们一样可使用gyro/acceleration
中其余非重力部分数据来添加交互方法。在这种情形下,咱们可使用CMDeviceMotionData
的userAcceleration
属性来让用户经过触碰设备左侧的方式来回退导航层级。
记得X轴左边对应负值。若是咱们能够感应到用户向作加速并大于2.5Gs的时候,这时候将促使咱们的试图控制器出栈。实现代码仅需数行:
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) } } }
效果是否是很棒,动图展现戳这里。
咱们从陀螺仪数据中能获取的加速数据并非惟一的好东东,同时咱们从中还能知道设备在空间中的朝向。咱们能够从CMDeviceMotionData
中attitude
属性获取CMAttitude
实例。CMAttitude
中含有三个能表明设备朝向的值:欧拉角度,四元组,还有一个旋转矩阵。
每一个CMAttitude
都与相对应的帧相关。
// 略一段。。。
- CMAttitudeReferenceFrameXArbitraryZVertical 描述一个设备平铺状态,Z轴垂直X轴任性。在实际中,当你第一次启动设备运动状态更新的时候X轴将会被修正为设备的朝向。
- CMAttitudeReferenceFrameXArbitraryCorrectedZVertical 本质上与上面相同可是在陀螺仪测试过程当中使用磁力仪进行校订。使用磁力仪将增长CPU的开销。
- CMAttitudeReferenceFrameXMagneticNorthZVertical 描述了设备平躺,而且X轴执行北极。这种设定可能须要你的用户进行八个方向的校准操做。
- CMAttitudeReferenceFrameXTrueNorthZVertical 与第三个相同,可是这种北极的校订差别故在除了磁力仪之后还须要地理位置数据。
咱们给出的意见是尽管“任性”。
三个维度的表达中,欧拉角度是最容易懂得,由于他们简单描述了咱们对每一个坐标轴转动。pitch
是X周方向的转动,增长的时候表示设备正朝你倾斜,减小的时候表示疏远;roll
是Y轴的转向,值减小的时候表示正往左边转,增长的时候往右;yaw
是Z轴转向,减小是时候是顺时针,增长的时候是逆时针。
上面的特性否知足右手定则。(是否是忽然想起了高中物理,咦不对我啥时候读太高中,高中是什么。。),即往手指指向方向走势正值,反之负值。
解谜游戏APP,朝向你的时候是题目+答案,背向你的时候是题目。
当出谜题的人点击按钮开始出题玩游戏,咱们首相确认交互 - 注意用initialAttitude
方法拉动下deviceMotion
:
// 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 configuration var 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
而后就是咱们所熟悉的方法调用startDeviceMotionUpdates
,咱们计算出三个欧拉角度的矢量大小而且使用它们做为展现谜题答案的触发器:
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) } } }
效果戳这里。
略
为了避免影响用户交互性,咱们通常把CoreMotionManager
的更新放在专属的线程队列而不是主线程队列。NSOperationQueue
提供了addOperationWithBlock
便于咱们实现:
et queue = NSOperationQueue() manager.startDeviceMotionUpdatesToQueue(queue) { [weak self] (data: CMDeviceMotion!, error: NSError!) in // motion processing here NSOperationQueue.mainQueue().addOperationWithBlock { // update UI here } }
最后做者交代了下误操做带来一些不要的用户体验,在使用CM的时候要通过深思熟虑。好的电子可让APP变得生动而且取悦用户。