iOS 陀螺仪和加速器

面试被问到了硬件,面试官具体指出陀螺仪,就简单研究下

首先必须真机测试,模拟器无效

应用场景:
1、指南针方向
2、计步数
3、摇一摇功能



加速器:检测设备在X、Y、Z轴上的加速度 (哪个方向有力的作用,哪个方向运动了)根据加速度数值,就可以判断出在各个方向上的作用力度

iOS 陀螺仪和加速器_第1张图片

陀螺仪:陀螺仪的主要作用,是基于角动量守恒的理论,沿着某个特定的坐标轴测量旋转速率。在使用中,陀螺仪的转子在高速旋转时,始终指向一个固定的方向,当运动物体的运动方向偏离预定方向时,陀螺仪就可以感受出来。
iOS 陀螺仪和加速器_第2张图片


源于苹果的运动框架CoreMotion.framework
它不仅仅提供给你获得实时的加速度值和旋转速度值,更重要的是,苹果在其中集成了很多算法,
可以直接给你输出把重力加速度分量剥离的加速度,省去你的高通滤波操作,以及提供给你一个专
门的设备的三维位置信息。



在CoreMotion中有2种获取数据方式:
Push方式:
提供一个线程管理器NSOperationQueue和一个回调Block,CoreMotion自动在每一个采样数据到来的时候回调这个Block,进行处理。在这种情况下,Block中的操作会在你自己的主线程内执行。
Pull方式:
你必须主动去向CMMotionManager要数据,这个数据就是最近一次的采样数据。你不去要,CMMotionManager就不会给你

获取重力方向

- (void)viewDidLoad {
    [super viewDidLoad];

    UIImageView *arrowImageView  = [[UIImageView alloc]initWithFrame:CGRectMake(50, 100, 200, 300)];
    arrowImageView.image = [UIImage imageNamed:@"arrow"];
    [self.view addSubview:arrowImageView];
    
    
    //创建运动管理者对象
    CMMotionManager *motionManager = [[CMMotionManager alloc]init];
    if (!motionManager.accelerometerAvailable) { //  判断 加速器是否可用
        return;
    }
//    motionManager.accelerometerUpdateInterval = 1/30.0; // 1 秒钟采样30次
    motionManager.accelerometerUpdateInterval = 2;
    //开始更新,后台线程开始运行。这是Pull方式。
//    [motionManager startAccelerometerUpdates];
    
    // push方法  加速度传感器开始采样,每次采样结果在block中返回
    [motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] 
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error)
     {
         //获取并处理加速度计数据
         CMAccelerometerData *newestAccel = motionManager.accelerometerData;
         double accelerationX = newestAccel.acceleration.x;
         double accelerationY = newestAccel.acceleration.y;
         // 返回值的单位为弧度,atan2(x,y)返回的是点(x,y)与x轴的夹角
         double ra = atan2(-accelerationY, accelerationX);
         arrowImageView.transform = CGAffineTransformMakeRotation(ra + M_PI_2);
     }];
}

这里获取间隔数值尽量调大,节约资源

计步器

有两种一种是调用iOS系统中的健康数据但不是实时的,这里主要讲一下实时获取步数的

在iOS8及其以上系统中我们可以使用CMPedometer类获取行走相关信息(步数、距离、上楼、下楼、当前速度等)
常用方法

当前设备是否可以获取步数
+ (BOOL)isStepCountingAvailable;

当前设备是否可以获取距离
+ (BOOL)isDistanceAvailable;

当前设备是否可以获取上下楼层数
+ (BOOL)isFloorCountingAvailable;

当前设备是否可以获取速度(s/m)
+ (BOOL)isPaceAvailable

当前设备是否可以获取节奏
+ (BOOL)isCadenceAvailable

根据开始和结束时间查询行走相关信息
- (void)queryPedometerDataFromDate:(NSDate )start toDate:(NSDate )end withHandler:(CMPedometerHandler)handler;

从某一时刻开始监测步数变化
- (void)startPedometerUpdatesFromDate:(NSDate *)start withHandler:(CMPedometerHandler)handler;

停止监测步数变化
- (void)stopPedometerUpdates;

先写一个model

计步用
@property(nonatomic,assign) int step;
//g是一个震动幅度的系数,通过一定的判断条件来判断是否计做一步
@property(nonatomic,assign) double g;

把 CMMotionManager 写成单例
.h

iOS 陀螺仪和加速器_第3张图片
image.png

.m 注意单例的写法

#import "StepManager.h"
#import "StepModel.h"
#import 
#import 

// 计步器开始计步时间(秒)
#define ACCELERO_START_TIME 2

// 计步器开始计步步数(步)
#define ACCELERO_START_STEP 1



@interface StepManager ()

{
    NSMutableArray *arrAll;                 // 加速度传感器采集的原始数组
}
@property (nonatomic) NSInteger startStep;                          // 计步器开始步数

@property (nonatomic, retain) NSMutableArray *arrSteps;         // 步数数组
@property (nonatomic, retain) NSMutableArray *arrStepsSave;     // 数据库纪录步数数组
@end

@implementation StepManager

static StepManager *sharedManager;
static CMMotionManager *motionManager;

+ (StepManager *)sharedManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (sharedManager == nil) {
            sharedManager = [[super allocWithZone:NULL] init];
        }
            motionManager = [[CMMotionManager alloc]init];
    });
    return sharedManager;
}

//开始计步
- (void)startWithStep
{
    if (!motionManager.isAccelerometerAvailable) {
        NSLog(@"加速度传感器不可用");
        return;
    }else {
        
        motionManager.accelerometerUpdateInterval = 1.0/40;
    }
    [self startAccelerometer];
  
}

- (void)startAccelerometer
{

        //如果不支持陀螺仪,需要用加速传感器来采集数据
        if (!motionManager.isAccelerometerActive) {//  isAccelerometerAvailable方法用来查看加速度器的状态:是否Active(启动)。
            
            // 加速度传感器采集的原始数组
            if (arrAll == nil) {
                arrAll = [[NSMutableArray alloc] init];
            }
            else {
                [arrAll removeAllObjects];
            }
            
            /*
             1.push方式
             这种方式,是实时获取到Accelerometer的数据,并且用相应的队列来显示。即主动获取加速计的数据。
             */
            NSOperationQueue *queue = [[NSOperationQueue alloc] init];
            
            [motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error){
                
                if (!motionManager.isAccelerometerActive) {
                    return;
                }
                
                //三个方向加速度值
                double x = accelerometerData.acceleration.x;
                double y = accelerometerData.acceleration.y;
                double z = accelerometerData.acceleration.z;
                //g是一个double值 ,根据它的大小来判断是否计为1步.
                double g = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) - 1;
                
                //将信息保存在步数模型中
                StepModel *stepsAll = [[StepModel alloc] init];

                stepsAll.g = g;
                // 加速度传感器采集的原始数组
                [arrAll addObject:stepsAll];
                
                // 每采集10条,大约1.2秒的数据时,进行分析
                if (arrAll.count == 10) {
                    
                    // 步数缓存数组
                    NSMutableArray *arrBuffer = [[NSMutableArray alloc] init];
                    
                    arrBuffer = [arrAll copy];
                    [arrAll removeAllObjects];
                    
                    // 踩点数组
                    NSMutableArray *arrCaiDian = [[NSMutableArray alloc] init];
                    
                    //遍历步数缓存数组
                    for (int i = 1; i < arrBuffer.count - 2; i++) {
                        //如果数组个数大于3,继续,否则跳出循环,用连续的三个点,要判断其振幅是否一样,如果一样,然并卵
                        if (![arrBuffer objectAtIndex:i-1] || ![arrBuffer objectAtIndex:i] || ![arrBuffer objectAtIndex:i+1])
                        {
                            continue;
                        }
                        StepModel *bufferPrevious = (StepModel *)[arrBuffer objectAtIndex:i-1];
                        StepModel *bufferCurrent = (StepModel *)[arrBuffer objectAtIndex:i];
                        StepModel *bufferNext = (StepModel *)[arrBuffer objectAtIndex:i+1];
                        //控制震动幅度,,,,,,根据震动幅度让其加入踩点数组,
                        if (bufferCurrent.g < -0.12 && bufferCurrent.g < bufferPrevious.g && bufferCurrent.g < bufferNext.g) {
                            [arrCaiDian addObject:bufferCurrent];
                        }
                    }
                    
                    //如果没有步数数组,初始化
                    if (nil == self.arrSteps) {
                        self.arrSteps = [[NSMutableArray alloc] init];
                        self.arrStepsSave = [[NSMutableArray alloc] init];
                    }
                    
                    // 踩点过滤
                    for (int j = 0; j < arrCaiDian.count; j++) {
                        StepModel *caidianCurrent = (StepModel *)[arrCaiDian objectAtIndex:j];
                        
                        //如果之前的步数为0,则重新开始记录
                        if (self.arrSteps.count == 0) {
                            
                            self.step = 0;
                            caidianCurrent.step = (int)self.step;
                            
                            [self.arrSteps addObject:caidianCurrent];
                            [self.arrStepsSave addObject:caidianCurrent];
                            
                        }
                        else {
                            
                                    if (self.startStep < ACCELERO_START_STEP) {//计步器开始计步步数 (步)
                                        
                                        self.startStep ++;
                                        break;
                                    }
                                    else if (self.startStep == ACCELERO_START_STEP) {
                                        self.startStep ++;
                                        // 计步器开始步数
                                        // 运动步数(总计)
                                        self.step = self.step + self.startStep;
                                    }
                                    else {
                                        self.step ++;
                                    }
        
                                    }
                                }
                        }
            }];
            
        }
}



// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
- (id)copyWithZone:(NSZone *)zone
{
    return  [StepManager sharedManager];
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
    return  [StepManager sharedManager];
}

+ (id) allocWithZone:(struct _NSZone *)zone
{
    return [StepManager sharedManager] ;
}


@end

使用页面调用

iOS 陀螺仪和加速器_第4张图片
image.png

摇一摇

iOS 系统在 UIResponder 类中提供了一个摇一摇的实现方法,大体实现思路为: 想让哪个 VC 实现摇一摇方案,将该 VC 变为第一响应者即可

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor blueColor];
    //  支持摇一摇  此处应该写在AppDelegate中,为粘贴代码方便写在这里
    [[UIApplication sharedApplication] setApplicationSupportsShakeToEdit:YES];
    //  让此控制器成为第一响应者
    [self becomeFirstResponder];
    
    
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self resignFirstResponder];
}


// 开始摇动
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    NSLog(@"开始摇动");
    return;
}



// 取消摇动 如来电话
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    NSLog(@"取消摇动");

    return;
}


//  摇动结束
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    if (event.subtype == UIEventSubtypeMotionShake) { // 判断是否是摇动结束
        
        SecondViewController *second = [SecondViewController new];
        [self.navigationController pushViewController:second animated:YES];
        
        NSLog(@"摇动结束");
    }
    return;
}

这是最简单的使用方法,但既然讲到加速器就用加速器在实现

加速器实现摇一摇

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor blueColor];
    self.motionManager = [[CMMotionManager alloc] init];//一般在viewDidLoad中进行
    self.motionManager.accelerometerUpdateInterval = 1.5;//加速仪更新频率,以秒为单位   
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self startAccelerometer];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receiveNotification:)
                                                 name:UIApplicationDidEnterBackgroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receiveNotification:)
                                                 name:UIApplicationWillEnterForegroundNotification object:nil];
}


- (void)startAccelerometer
{
    //以push的方式更新并在block中接收加速度
    [self.motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc]init]
                                             withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
                                                 [self outputAccelertionData:accelerometerData.acceleration];
                                                 if (error) {
                                                     NSLog(@"motion error:%@",error);
                                                 }
                                             }];
}


- (void)outputAccelertionData:(CMAcceleration)acceleration
{
    //综合3个方向的加速度
    double accelerameter =sqrt( pow( acceleration.x , 2 ) + pow( acceleration.y , 2 )
                               + pow( acceleration.z , 2) );
    //当综合加速度大于2.3时,就激活效果(此数值根据需求可以调整,数据越小,用户摇动的动作就越小,越容易激活,反之加大难度,但不容易误触发)
    if (accelerameter>2.3f) {
        //立即停止更新加速仪(很重要!)
        [self.motionManager stopAccelerometerUpdates];
         __weak typeof(self) WeakSelf = self;

        dispatch_async(dispatch_get_main_queue(), ^{  
            //UI线程必须在此block内执行,例如摇一摇动画、UIAlertView之类
            // 第二个页面
            SecondViewController *second = [SecondViewController new];
            [WeakSelf.navigationController pushViewController:second animated:YES];
        });  
    }      
}


- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    //停止加速仪更新(很重要!)
    //当App退到后台时必须停止加速仪更新,回到当前时重新执行。否则应用在退到后台依然会接收加速度更新,可能会与其它当前应用冲突
    [self.motionManager stopAccelerometerUpdates];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIApplicationDidEnterBackgroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIApplicationWillEnterForegroundNotification object:nil];
}


//对应上面的通知中心回调的消息接收
- (void)receiveNotification:(NSNotification *)notification
{
    if ([notification.name
         isEqualToString:UIApplicationDidEnterBackgroundNotification])
    {
        [self.motionManager stopAccelerometerUpdates];
    }else{
        [self startAccelerometer];
    }
}


// 必须要 移除观察者,
- (void)dealloc{
    // 移除本控制器所有的通知;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

你可能感兴趣的:(iOS 陀螺仪和加速器)