您的位置:首页 > 移动开发 > IOS开发

CMDevice​Motion Written by Nate Cook (译)

2017-09-23 11:17 369 查看
在每个iPhone的闪亮的光滑屏幕下面,安装着鲜为人知的陀螺仪和加速器,它们位于屏幕和SoCs的逻辑板上,而然,在很多时候它们被忽略了。

为什么会变成这样? Core Motion 这个框架让我们很容易的去使用这些传感器,它使得交互起来就像为我们日常打开水龙头和刷卡一样简单。

对于装有M7或M8运动处理器的设备, Core Motion 还为这些提供了可以访问存储在本地运动数据的功能,例如:步数,爬楼梯数,以及步行和骑车等运动类型。

Core Motion 允许开发人员检查内置的传感器(包括加速器,陀螺仪和磁力计)中的原始数据,从而观察和响应IOS设备的运动类型和方向。

加速器和陀螺仪的数据都是通过IOS设备上的三个轴来表示的,对于以纵向方向保持的iPhone,X轴从左(负值)到右(正值)穿过设备,Y轴通过设备从底部( - )到顶部(+),Z -axis从背面( - )到前面(+)垂直地穿过屏幕。

合成的设备运动数据将以几种不同的方式呈现出来,每种数据中都有自己的用途,我们将在文章的下面看到。



CMMotionManager

CMMotionManager这个类提供访问IOS设备上所有的运动数据的接口,有趣的是,Core Motion 提供了 “pull”操作和“push”操作来访问运动数据。对于“pull”的数据,你可以访问CMMotionManager属性中,任何状态为只读的传感器对象或者合成数据对象。对于接受到的“pushed”的数据,你可以通过block或者闭包在指定的间隔内获取更新所需的数据。

为了保持高性能,apple建议在你的app中将CMMotionManager作为单例使用。

CMMotionManager提供了一致的接口来访问:accelerometer,gyro,magnetometer,和deviceMotion。例如,以下是陀螺仪的交互方式,使用运动数据来代替你想要的gyro中的数据。

检查可用性

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


为了使Swift和Objective-C之间更简单和等效,假设我们已经将一个manager实例声明为所有示例的视图控制器属性


设置更新间隔

manager.gyroUpdateInterval = 0.1


这是一个TimeInterval值,为了指定你的更新时间在几秒钟内:值越小响应越顺畅,值越大,CPU使用率越低。

开始更新“pull”操作的数据

manager.startGyroUpdates()这样调用以后,能通过 manager.gyroData 随时访问设备陀螺仪中的数据。

开始更新“push”操作的数据

manager.startGyroUpdates(to: queue) { (data, error) in
// ...
}


这个block将在指定的频率内进行不断的回调。

停止更新

manager.stopGyroUpdates()


使用加速度计感器

假设我们想给我们的应用程序的启动页面一个有趣的效果,无论手机如何倾斜,背景图像保持水平。

请考虑以下代码:

首先,我们检查一下加速度计感器是否可用,接下来我们制定一个非常高的更新频率,然后我们将开始更新这个闭包,在闭包内旋转一个UIImageView对象。

RotationViewController * __weak weakSelf = self;
if (manager.accelerometerAvailable) {
manager.accelerometerUpdateInterval = 0.01f;
[manager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMAccelerometerData *data, NSError *error) {
double rotation = atan2(data.acceleration.x, data.acceleration.y) - M_PI;
weakSelf.imageView.transform = CGAffineTransformMakeRotation(rotation);
}];
}


每个CMAccelerometerData的数据包都含有一个x,y,z的值,这些值表示该轴方向加速度的Gs(其中G是一个重力单位)也就是说,如果你的设备是竖直静止的,它的加速度值是(0,-1,0),正面朝上平躺在桌子上加速度将会是(0,0,-1),右倾斜45度加速度将会是(0.707,-0.707,0)。

我们以加速度传感器中的x,y数据来通过arctan2函数来计算CGAffineTransform所需的旋转角度。不管我如何旋转手机,我们的图片都将保持在右边,这是在国家航空航天博物馆应用程序内(是我小时候最喜欢的博物馆):



旋转结果不是令人很满意,图像在旋转过程中产生抖动,并且在空间中移动设备时会对加速器产生更多的影响。当我们多抽取几组读取到的数据并计算它们的平均值可以缓解这些问题,但是还是让我们来看看当用陀螺仪时会发生些什么吧。

添加陀螺仪

我们可以从startGyroUpdates…中获取陀螺仪和加速度计的合成数据,而不是获取原始陀螺仪中deviceMotion中的数据。使用陀螺仪,Core Motion将用户移动与重力加速分离,并将它们作为CMDeviceMotion实例的属性。这个代码非常类似于我们的第一个例子:

RotationViewController * __weak weakSelf = self;
if (manager.deviceMotionAvailable) {
manager.deviceMotionUpdateInterval = 0.01f;
[manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *data, NSError *error) {
double rotation = atan2(data.gravity.x, data.gravity.y) - M_PI;
weakSelf.imageView.transform = CGAffineTransformMakeRotation(rotation);
}];
}


好多了!



UIClunkController

我们还可以使用复合陀螺仪/加速度数据的其他非重力部分来添加新的交互方法。在这种情况下,让我们使用userAcceleration属性CMDeviceMotion来向后导航,只要用户用敲击她的设备的左侧。

请记住,X轴横向穿过我们手中的设备,左侧为负值。如果我们感觉左侧的用户加速度超过2.5 Gs,那么这将是从堆栈中弹出视图控制器的提示。实现的方式只是与之前的的例子只有几行代码不一样而已:

ClunkViewController * __weak weakSelf = self;
if (manager.deviceMotionAvailable) {
manager.deviceMotionUpdateInterval = 0.01f;
[manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *data, NSError *error) {
if (data.userAcceleration.x < -2.5f) {
[weakSelf.navigationController popViewControllerAnimated:YES];
}
}];
}


它的效果很吸引人——点击设备进入详情然后立即把我们带回到展览列表中:



得到一个Attitude

通过陀螺仪数据,我们不仅能够获得更好的加速器数据,还能知道设备在空间中真实的方向。我们发现这个数据在CMDeviceMotion的attitude属性中,一个CMAttitude实例。CMAttitude。CMAttitude包含设备方向的三种不同表示:欧拉角,四元数和旋转矩阵。它们中的每一个都与给定的参考系有关。

寻找参考系:

你可以考虑将attitude的计算方向作为设备的参考坐标系。随着特殊方向的指向性增加,所有四个可能的参考坐标系描述了一个躺在桌子上的设备,

CMAttitudeReferenceFrame.xArbitraryZVertical

描述了具有“任意”X轴的平躺的设备,实际上,当你首次启动设备运动更新时,X轴是固定在设备的方位。

CMAttitudeReferenceFrame.xArbitraryCorrectedZVertical

基本上是相同的,但使用磁力计来校正陀螺仪随时间的测量的可能变化。使用磁力计增加一个CPU(加上电池)成本。

CMAttitudeReferenceFrame.xMagneticNorthZVertical

描述了一种平坦的设备,其X轴(即,设备的右侧)指向磁北。此设置可能需要用户使用其设备执行该图8运动来校准磁力计。

CMAttitudeReferenceFrame.xTrueNorthZVertical

与最后一个相同,但是这适用于磁性/真正的北偏差,因此除了磁力计之外还需要位置数据。

为了我们的目的,默认的“任意”参考坐标系将会很好 - 你一会儿看到为什么这样说。

欧拉角

在三种attitude的表示中,欧拉角是最容易理解的,因为它们简单地描述了我们已经在使用的每个轴的旋转。pitch是围绕X轴的旋转,随着装置朝向你的倾斜而增加,随着远离你倾斜而减小;roll是围绕Y轴的旋转,随着装置向左旋转而减小,向右增加; 并且yaw是围绕(垂直)Z轴的旋转,顺时针减小,逆时针增加。

这些值中的每一个都遵循所谓的“右手规则”:伸出大拇指,并将拇指指向三个轴中的任何一个方向。转向指向你的指尖是正的,转身是负面的。

你自己留着

最后,让我们尝试使用设备的attitude,为flash-card的应用程序进行新的交互,以供两位学习伙伴使用。代替在提示和答案之间手动切换,我们会在设备转向时自动切换视图,所以测验者会看到答案,而被测试者只能看到提示。

从参考坐标系中取出这个开关将是困难的。需要知道要监视哪些角度,我们需要考虑设备的启动方向,然后确定设备指向哪个方向。相反,我们可以保存一个CMAttitude实例,并将其用作调整后的欧拉角度的“零点”,称之为multiply(byInverseOf:)转换成所有attitude更新的方法。

当测验者点击按钮开始测验时,我们首先配置交互 - 记下deviceMotion的“pull” initialAttitude:

// --- class method to get magnitude of vector via Pythagorean theorem
+ (double)magnitudeFromAttitude:(CMAttitude *)attitude {
return sqrt(pow(attitude.roll, 2.0f) + pow(attitude.yaw, 2.0f) + pow(attitude.pitch, 2.0f));
}

// --- In @IBAction handler
// initial configuration
CMAttitude *initialAttitude = manager.deviceMotion.attitude;
__block BOOL showingPrompt = NO;

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


然后,在我们现在调用我们熟悉的startDeviceMotionUpdates,我们计算由三个欧拉角描述的向量的大小,并将其用作显示或隐藏提示视图的触发器:

FacingViewController * __weak weakSelf = self;
if (manager.deviceMotionAvailable) {
[manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *data, NSError *error) {

// translate the attitude
[data.attitude multiplyByInverseOfAttitude:initialAttitude];

// calculate magnitude of the change from our initial attitude
double magnitude = [FacingViewController magnitudeFromAttitude:data.attitude];

// show the prompt
if (!showingPrompt && (magnitude > showPromptTrigger)) {
showingPrompt = YES;

PromptViewController *promptViewController = [weakSelf.storyboard instantiateViewControllerWithIdentifier:@"PromptViewController"];
promptViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[weakSelf presentViewController:promptViewController animated:YES completion:nil];
}

// hide the prompt
if (showingPrompt && (magnitude < showAnswerTrigger)) {
showingPrompt = NO;
[weakSelf dismissViewControllerAnimated:YES completion:nil];
}
}];
}


完成这一切后,让我们来看看互动。当设备旋转时,显示屏会自动切换视图,被测试者从不会看到答案:



进一步阅读

我撇去了CMAttitude中的四元数和旋转矩阵,但我不是故意的;特别是四元数,它非常有意思,如果你有足够的时间学习它,它将会打开你更多的思维。

队列

为了保持代码示例的可读性,我们将所有CoreMotionManager的更新操作发送到主队列。最佳做法是最好在自己的队列中进行这些更新操作,这样就不会减慢用户交互的速度,但是我们需在主队列中更新用户界面元素。使用NSOperationQueue的addOperationWithBlock方法很容易实现:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[manager startDeviceMotionUpdatesToQueue:queue
withHandler:
^(CMDeviceMotion *data, NSError *error) {
// motion processing here

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update UI here
}];
}];


最后一个注意事项:显然,Core Motion并适用于所用的用户交互,通过motion的导航是非常有趣的,但是却很难被用户发现这个操作。无目的的使用这些动画可能会使你难以专注于手头的任务。好的程序员不会浪费时间在这些把戏上面,他们会将motion用在更加有意义的地方使得用户在用他们的应用程序是更加流畅。

(若翻译中有明显错误,还请各位指出,谢谢)

原文链接:http://nshipster.com/cmdevicemotion/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息