Core Motion 的那些事

获取设备的移动事件

当我们移动,晃动,或者倾斜手机的时候,这些动作都会被设备的硬件捕获。其实每一个动都都会在 X, Y, Z 三个方向上产生速度上的变化。根据不同的变化我们可以检测出来设备的朝向和移动。要检测设备的朝向和移动,我们有三种方法:

  • 利用 UIDevice,我们可以获取设备的大致方向,例如屏幕朝上还是朝下。
  • 利用 UIKit 中的 UIEvent,我们可以获取设备的晃动
  • 利用 Core Motion 来精确的获取设备在 X Y Z 三个轴上的变化。这个是最精确的。

使用 UIDevice 来获取设备的方向

首先我们应该告诉 UIDevice 来检测设备方向变化,然后我们就可以在 UIDevice 中的 orientation 属性得到设备方向的变化。

typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
    UIDeviceOrientationUnknown,
    UIDeviceOrientationPortrait,            // Device oriented vertically, home button on the bottom
    UIDeviceOrientationPortraitUpsideDown,  // Device oriented vertically, home button on the top
    UIDeviceOrientationLandscapeLeft,       // Device oriented horizontally, home button on the right
    UIDeviceOrientationLandscapeRight,      // Device oriented horizontally, home button on the left
    UIDeviceOrientationFaceUp,              // Device oriented flat, face up
    UIDeviceOrientationFaceDown             // Device oriented flat, face down
} __TVOS_PROHIBITED;

如果你想检测设备的方向变化,那么你应该监听 UIDeviceOrientationDidChangeNotification 通知,系统会在设备方向发生变化的时候发出该通知。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    NSLog(@"%ld",[UIDevice currentDevice].orientation);
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cheange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

- (void)cheange:(NSNotification *)notification {
    UIDevice *device = notification.object;
    NSLog(@"%ld", device.orientation);
}

为了延长电池的使用寿命,我们应该在不使用的时候告诉 UIDevice 来关闭加速计。

- (void)viewDidDisappear:(BOOL)animated {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
}

使用 UIEvent 来获取设备摇晃事件

当用户晃动设备的时候,iOS 系统会评估加速计的数据,当这些数据特征符合摇晃的时候,系统会就会认为用户在摇晃设备,然后会创建一个 UIEvent 对象来发送个宿主 APP,让其处理。
移动事件和点击事件很像。系统会告诉 APP 一个运动的开始和结束,但并不是每一个动作发生时都会这么做。
检测晃动超级简单。晃动这个是可以在模拟器中试验的。

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    NSLog(@"shake");
}

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    NSLog(@"shake");
}
shake

设置并检查硬件是否支持设备移动事件

如果你需要获取加速计数据,你应该在 Info.plist 中添加相应的键值对,如果在一个没有陀螺仪的设备上打开需要陀螺仪的应用,那么将会崩溃。App Store 也会根据你所需要的硬件能力来提醒用户来是否要下载一个打不开的 APP。

如果你只是获取设备的大致方向,可以不用添加

使用 Core Motion 来捕获设备的移动

Core Motion 框架提供加速计和陀螺仪的原始数据给 APP 处理,它使用特别的算法来处理收集到的数据,精确度更高,而且是在自己的线程中处理。

Core Motion 和 UIKit 是不同的,它和 UIEvent 无关,也不随着响应链传递,它只是直接的交付数据。
Core Motion 所产生的数据有三类

  • CMAltimeter 高度计
  • CMPedometer 计步器
  • CMAccelerometerData 捕获每一个轴上的加速度
  • CMGyroData 捕获三个轴上的旋转率
    *CMDeviceMotion 包含了几个不同的测量,数据更加精确
    一般来说,一个 APP 只需要创建一个 CMMotionManager 对象,设置其更新间隔,请求数据,然后处理,每一个数据类都是 CMLogItem 的子类,所有的数据都会被打上时间的标记并且可以输出到日志文件中。
    CMMotionManager 有两种获取数据的方法,pull push
  • pull APP 主动去请求数据更新
  • push APP 调用加速计后,实现回调的 block,当加速计有数据更新的时候,就回去调用这个 block,然后在其中处理相应的数据即可
    一般来说使用 pull 更为方便,因为它的代码更少,用到了再去取相应的数据。
    不用的时候,记得关闭传感器,这样更加省电。

选择一个合适的更新时间

当你使用 Core Motion 的时候,你需要指定一个更新间隔。这个时间应该是你的 APP 所能接收的最大间隔,因为这样,可以提升电池的续航。下边这个表格提供了常用的更新频率,很少有 APP 需要每秒钟获取 100 次的加速计数据。

事件频率(HZ) 用途
10 - 20 确定设备的当前朝向
30 - 60 游戏或者以设备加速度作为实时的用户输入数据
70 - 100 适用于 APP 需要高频率的运动事件,例如,可以根据这个来判断用户快速点击和摇晃设备

使用高度计 CMAltimeter

在手机中,一般使用的是气压高度计,根据当前位置的大气压,获取当前位置的高度。具体公式为

z=cT log(P0/P)
c 为常数,T 为绝对温度,P 为在高度 z 处的气压,P0 为在海平面处的大气压,常数 c 与重力加速度和空气的摩尔质量相关

我们知道,当高度上升12米,气压计中的汞柱下降1mm,标准大气压下是760mm的汞柱,一百帕相当于 3/4 mm的汞柱 我们可以得到简单的计算公式

当前高度 =(760 - 当前大气压 * 3 / 4)*12

我们使用 + isRelativeAltitudeAvailable; 来判断高度计是否可用,使用 - startRelativeAltitudeUpdatesToQueue: withHandler: 来获得当前高度,在这个回调 block 中,我们会得到一个 CMAltitudeData 对象,该对象有两个属性,一个是当前位置的大气压,单位是 kPa,一个是当前位置,相对于起始测量位置的高度。例如,当你刚开始测量的时候,该值为 0 ,当你拿着设备运动的时候,这个值会返回你当前位置,相对于起始位置的高度,具体高度,可以根据当前位置的压强计算出来,但如果遇到气压波动比较大的位置,高度误差,还是相当大的。

使用计步器 CMPedometer

当人携带者设备走动的时候,会产生一定的频率,不同的运动产生的频率是不一样的,或走,或坐,或跑,根据不同的频率滤波,识别,最终可以大致确定设备持有者的运动状态。
在 iOS 8 以后,我们使用 CMPedometer ,而在 iOS 7 - iOS 8 我们使用 CMStepCounter 来获取数据
在 CMPedometer, 我们可以获取一下数据

  1. 步数
  2. 距离
  3. 楼层变化
  4. 速度
  5. 节奏
  6. 运动的开始暂停

当你开始调用的时候,即使应用被压到后台,系统还是可以记录步数,当你再次返回的时候,系统会把这段时间积累的步数全部返回给你。我们可以使用系统提供的 API 来查询某一段时间的运动数据,但是,系统最多只为我们保存 7 天。

//从某个时间开始记录步数
-startPedometerUpdatesFromDate: withHandler             
//查询某个时间的段的数据
-queryPedometerDataFromDate:toDate:withHandler
//停止记录步数
-stopPedometerUpdates

使用 Core Motion 获取加速计数据

加速计测量了三个轴的速度改变。在 Core Motion 中,每一个运动都被捕获进一个 CMAccelerometerData 对象,而这个对象包含了一个 CMAcceleration 结构体。


acceleration_axes.png

首先创建一个 CMMotionManager 对象

  • 使用 pull 方式获取数据,当调用 startAccelerometerUpdates 方法后,Core Motion 会用最新的测量数据更新 CMMotionManager 类的 accelerometerData 属性,之后你可以在一个循环中或者定时器中获取 accelerometerData 来判断设备的运动。
  • 使用 push 方式来获取数据,当调用 startAccelerometerUpdatesToQueue:withHandler: 当数据更新的时候,就会自动调用 handler,在这个 block 中,你可以处理 accelerometerData
    self.mManager = [[CMMotionManager alloc] init];
    if (self.mManager.accelerometerAvailable) {
        NSLog(@"available");
    }
    self.mManager.accelerometerUpdateInterval = 1;
    // pull
    [self.mManager startAccelerometerUpdates];
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%f", self.mManager.accelerometerData.acceleration.x);
    }];
    
    // push
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"accelerometer";
    [self.mManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        NSLog(@"x : %f", accelerometerData.acceleration.x);
        NSLog(@"y : %f", accelerometerData.acceleration.y);
        NSLog(@"z : %f", accelerometerData.acceleration.z);
    }];
    // stop 
    - (void)stopUpdates {

     if ([self.mManager isAccelerometerActive] == YES) {
          [self.mManager stopAccelerometerUpdates];
     }
}

处理陀螺仪数据

角速度传感器,测量物体的偏转,角速度。在手机上,单凭加速器是无法构建完整的 3D 动作,加上陀螺仪,可以让我们清晰感测到手机的旋转。 陀螺仪测量设备绕着 X Y Z 三个轴的自传速率

陀螺仪

当你请求陀螺仪数据更新的时候,Core Motion 返回的数据是有误差的。它会返回一个 CMGyroData 对象,该对象中有一个 rotationRate 属性,它是一个 CMRotationRate 结构体,里边包含了三个方向上的自传速率,单位是弧度每秒。 用 CMGyroData 是有误差的,如果想获取更为精确是自传速率,可以使用 CMDeviceMotion 。
其更新数据方式也是有两种,分为 startGyroUpdates pull 和 startGyroUpdatesToQueue:withHandler: push,具体方式和加速计获取方式一样,取的数据在 CMMotionManager 的 gyroData 属性中,同样,你也需要设置更新的时间间隔。
代码就不贴了,和上边大同小异。

旋转方向

旋转方向遵循右手定则,右手握拳,大拇指指向某一个轴,如果旋转的方向和四个手指的方向一样的话,即为正,否则就为负

处理设备数据

如果设备上既有加速计又有陀螺仪的话,Core Motion 提供了一个 device-motion 的服务用来处理两个传感器的原始数据。使用这个,可以得到最精确的数据。
你可以访问这个 CMDeviceMotion 对象的 attitude 属性,每一个 CMAttitude 对象包含了下边三种数据

  1. 一个四元组
  2. 一个旋转率矩阵
  3. 欧拉角

调用方式和上边测量加速度发方式类似,代码就不贴了。

关于欧拉角

描述一个物体的旋转,往往是最难的。欧拉角是表达旋转的最简单的一种方式,形式上它是一个三维向量,其值分别代表物体绕坐标系三个轴(x,y,z轴)的旋转角度。

pitch.gif
roll

yaw.gif

但是,这么描述的话,会有一个问题,那就是万向锁。

万向锁.jpg

欧拉旋转可以靠这种顺序让一个物体指到任何一个想要的方向,但如果在旋转中不幸让某些坐标轴重合了就会发生万向节锁,这时就会丢失一个方向上的旋转能力,也就是说在这种状态下我们无论怎么旋转(当然还是要原先的顺序)都不可能得到某些想要的旋转效果。具体可以点这里来了解。
由于大多数都是物理知识,我这个农民工,了解的还不是很详细,数据我们是得到了,具体怎么应用到现实中,还要慢慢摸索。

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

推荐阅读更多精彩内容