一. 簡介
iOS設(shè)備內(nèi)置了一些傳感器,并提供了相關(guān)的API供調(diào)用,關(guān)于iOS傳感器的類型及作用等相關(guān)知識(shí),可以查看文章iOS開發(fā)之傳感器 ,本篇文章的demo的github地址是:傳感器的使用,感興趣的可以瞄瞄。
傳感器 | 涉及的類 | 作用 |
---|---|---|
光線傳感器(Ambient Light ) | UIScreen、AVCaptureSession | 自動(dòng)調(diào)節(jié)屏幕亮度、手電筒的開啟(mobile有) |
距離傳感器(Proximity) | UIDevice | 打電話自動(dòng)鎖屏 |
計(jì)步器(Pedometer) | CMPedometer | 運(yùn)動(dòng)數(shù)據(jù)測量 |
加速計(jì)(Accelerometer) | CMMotionManager | 計(jì)步器、搖一搖 |
陀螺儀(Gyroscope) | CMMotionManager | 游戲控制 |
磁力計(jì)(Magnetometer) | CMMotionManager | 磁力感應(yīng) |
濕度傳感器(Moisture) | 維修時(shí)檢測設(shè)備進(jìn)水與否 | |
內(nèi)部溫度傳感器(Internal Temperature) |
二. 光線強(qiáng)弱測量
蘋果有相關(guān)的私有api提供光線檢測,但不允許上架產(chǎn)品使用,所以間接地使用其它方式檢測光線強(qiáng)弱,有興趣的小伙伴可以去研究下<GraphicsServices/GraphicsServices.h>中的GSEventSetBacklightLevel()方法
-
檢測屏幕亮度
1. UIScreenBrightnessDidChangeNotification:光線變化通知
2. [UIScreen mainScreen].brightness:獲取屏幕亮度光線亮度調(diào)節(jié) 1. 對(duì)手機(jī)自動(dòng)亮度調(diào)節(jié)進(jìn)行設(shè)置,設(shè)置-->顯示與亮度-->允許自動(dòng)亮度調(diào)節(jié),當(dāng)手機(jī)感受到外界光線亮度變化時(shí),會(huì)自動(dòng)調(diào)節(jié)屏幕亮度 2. 手動(dòng)調(diào)節(jié)屏幕亮度
-
攝像頭檢測
"通過對(duì)攝像頭捕獲的視頻流的參數(shù)進(jìn)行分析,亮度的參數(shù)key是kCGImagePropertyExifBrightnessValue"// AVCaptureVideoDataOutputSampleBufferDelegate代理方法 // 攝像頭輸出的視頻流 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // 獲取視頻流的相關(guān)參數(shù) CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate); NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict]; CFRelease(metadataDict); "exif中有個(gè)brightness參數(shù)值" NSDictionary *exifMetadata = [metadata[(NSString *)kCGImagePropertyExifDictionary] mutableCopy]; float brightnessValue = [exifMetadata[(NSString *)kCGImagePropertyExifBrightnessValue] floatValue]; // 在UI上顯示 self.cameraBightnessLbl.text = [NSString stringWithFormat:@"%.2f",brightnessValue]; }
順便將攝像頭捕獲視頻流的方法貼出來
- (AVCaptureSession *)initCaptureSession {
// 創(chuàng)建會(huì)話
NSError *error;
AVCaptureSession *captureSession = [AVCaptureSession new];
_captureSession = captureSession;
// 輸入:攝像頭
AVCaptureDevice *cameraDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:cameraDevice error:&error];
if ([captureSession canAddInput:cameraDeviceInput]) {
[captureSession addInput:cameraDeviceInput];
}
// 輸出:視頻數(shù)據(jù)
"當(dāng)有視頻數(shù)據(jù)輸出時(shí),會(huì)調(diào)用上面的AVCaptureVideoDataOutputSampleBufferDelegate代理方法"
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
if ([captureSession canAddOutput:output]) {
[captureSession addOutput:output];
}
// 實(shí)時(shí)預(yù)覽:展現(xiàn)給用戶
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
previewLayer.frame = self.lightVideoV.bounds;
[self.lightVideoV.layer addSublayer:previewLayer];
}
三. 距離傳感器檢測
1. 距離傳感器默認(rèn)是關(guān)閉的,需手動(dòng)開啟
[UIDevice currentDevice].proximityMonitoringEnabled = YES/NO;
2. 傳感器開啟后,通過通知監(jiān)聽是否有物體靠近
1. 通知名:UIDeviceProximityStateDidChangeNotification;
2. 返回對(duì)象:notification.object會(huì)返回UIDevice對(duì)象;
3. 判斷:UIDevice對(duì)象的proximityState屬性指示是否靠近用戶,YES(靠近)/NO(離開)
3. 代碼
- (IBAction)proximitySwitch:(UISwitch *)sender {
// 傳感器的開啟與關(guān)閉
[UIDevice currentDevice].proximityMonitoringEnabled = sender.on;
// 可用性檢測
if (sender.on && ![UIDevice currentDevice].proximityMonitoringEnabled) {
sender.on = NO;
[self showWithTitle:@"距離傳感器不可用" message:nil];
return;
}
// 通知監(jiān)聽狀態(tài)
if (sender.on) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(proximityStateChange:) name:UIDeviceProximityStateDidChangeNotification object:nil
];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceProximityStateDidChangeNotification object:nil];
}
}
- (void)proximityStateChange:(NSNotification *)noti {
UIDevice *device = noti.object;
if ([device isKindOfClass:[UIDevice class]]) {
// 是否有物體靠近
NSLog(@"%@", (device.proximityState? @"物體靠近" : @"物體離開"));
}
}
四. 計(jì)步器
ios7-ios8的時(shí)候使用CMStepCounter,ios8及以后使用CMPedometer,下面介紹的是CMPedometer
計(jì)步器能獲取的數(shù)據(jù)
1. startDate:開始時(shí)間段
2. endDate:結(jié)束時(shí)間段
3. numberOfSteps:步數(shù)
4. distance:距離
5. floorsAscended:爬樓數(shù)
6. floorsDescended:下樓數(shù)
7. ios9以后的系統(tǒng)才有
1. currentPace:當(dāng)前步速 in seconds per meter
2. currentCadence:當(dāng)前步頻 in steps per second
8. ios10的
1. averageActivePace:平均活躍時(shí)間段的步速-
使用
"運(yùn)動(dòng)與健康"權(quán)限設(shè)置
1. 使用計(jì)步器需添加權(quán)限NSMotionUsageDescription描述
2. 第一次使用CMPedometer對(duì)象的時(shí)候系統(tǒng)自動(dòng)會(huì)向用戶請求"運(yùn)動(dòng)與健康"授權(quán)
3. 授權(quán)判斷:[CMSensorRecorder isAuthorizedForRecording];
4. 沒找到授權(quán)的方法:不過由于系統(tǒng)會(huì)自動(dòng)授權(quán)(第2條),通過下面的方法也可以達(dá)到授權(quán)的效果,
該方法在獲取到用戶的選擇之后才會(huì)進(jìn)行回調(diào)
[self.pedometer queryPedometerDataFromDate:toDate:withHandler:]-
獲取數(shù)據(jù)
1. CMPedometer中的對(duì)象方法
// 獲取某時(shí)間段的歷史數(shù)據(jù)
- (void)queryPedometerDataFromDate:(NSDate *)start toDate:(NSDate *)end withHandler:(CMPedometerHandler)handler;
// 從某時(shí)間段開始實(shí)時(shí)獲取數(shù)據(jù),停止獲取的時(shí)候需調(diào)用stopPedometerUpdates方法
- (void)startPedometerUpdatesFromDate:(NSDate *)start withHandler:(CMPedometerHandler)handler;
// 獲取計(jì)步器的狀態(tài)(暫停/記步),停止獲取的時(shí)候需調(diào)用stopPedometerEventUpdates方法
- (void)startPedometerEventUpdatesWithHandler:(CMPedometerEventHandler)handler NS_AVAILABLE(NA,10_0) __WATCHOS_AVAILABLE(3_0);
// 停止更新數(shù)據(jù)
- (void)stopPedometerUpdates;
// 停止?fàn)顟B(tài)更新
- (void)stopPedometerEventUpdates NS_AVAILABLE(NA,10_0);2. 主要的代碼實(shí)現(xiàn) // 1. 初始化計(jì)步器self.pedometer - (CMPedometer *)pedometer { if (!_pedometer) { _pedometer = [[CMPedometer alloc] init]; } return _pedometer; } // 2. 通過一個(gè)開關(guān)開啟/關(guān)閉記步功能 - (IBAction)recordStepCount:(UIButton *)sender { BOOL start = !sender.selected; // 開始記步 if(start){ // 可用性檢測 if(![CMPedometer isStepCountingAvailable]){ [self showWithTitle:@"計(jì)步器不可用" message:nil]; return; } // 1 授權(quán) // 1.1 pedometer第一次被使用時(shí),會(huì)由系統(tǒng)主動(dòng)提示用戶授權(quán)“運(yùn)動(dòng)與健康”;但沒找到授權(quán)的相關(guān)方法,通過該方式實(shí)現(xiàn)需求 __weak typeof (self) weakSelf = self; [self.pedometer queryPedometerDataFromDate:[NSDate date] toDate:[NSDate date] withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) { // 1.2 用戶選擇了授權(quán)與否之后,該block才會(huì)被調(diào)用,不在主線程 dispatch_async(dispatch_get_main_queue(), ^{ // 1.3 授權(quán)判斷 if(![CMSensorRecorder isAuthorizedForRecording]){ [weakSelf showWithTitle:@"未授權(quán)" message:@"前往設(shè)置->隱私->運(yùn)動(dòng)與健康,點(diǎn)擊允許訪問"]; return; } sender.selected = YES; // 2. 獲取數(shù)據(jù) // 2.1 監(jiān)測計(jì)步器狀態(tài):暫停、恢復(fù) [weakSelf.pedometer startPedometerEventUpdatesWithHandler:^(CMPedometerEvent * _Nullable pedometerEvent, NSError * _Nullable error) { NSLog(@"%@",pedometerEvent.type==CMPedometerEventTypePause? @"暫停":@"繼續(xù)"); }]; // 2.2 監(jiān)測計(jì)步器數(shù)據(jù) [weakSelf.pedometer startPedometerUpdatesFromDate:[NSDate date] withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) { if (pedometerData) { // 2.3 處理數(shù)據(jù):回調(diào)不在主線程,所以需要回到主線程處理 dispatch_async(dispatch_get_main_queue(), ^{ // 數(shù)據(jù)存儲(chǔ)在pedometerData中 ... }); } }]; }); }]; } else { // 結(jié)束記步 sender.selected = NO; [self.pedometer stopPedometerUpdates]; [self.pedometer stopPedometerEventUpdates]; } }
四. 加速計(jì)/陀螺儀/磁力計(jì)
- 加速計(jì)/陀螺儀/磁力計(jì)這三種感應(yīng)器使用也很簡單,直接通過CMMotionManager對(duì)象處理,獲取數(shù)據(jù)的方法大同小異;
- CMMotionManager對(duì)象可以檢測設(shè)備的可用性、獲取數(shù)據(jù)、設(shè)置數(shù)據(jù)的更新頻率,有需要的可以直接去頭文件看看
- 通過計(jì)步器制作搖一搖、計(jì)步器:按我理解呢,如果考慮周全,搖一搖、記步也需要比較復(fù)雜的算法,自己做難免不全面;并且這些蘋果已經(jīng)有對(duì)應(yīng)API,直接調(diào)用就好
- CMMotion的使用可以參考這篇文章詳說CMDeviceMotion
由于這三種感應(yīng)器獲取數(shù)據(jù)方式一致,就只對(duì)加速計(jì)進(jìn)行舉例說明
-
成員變量及方法
// 存儲(chǔ)加速計(jì)數(shù)據(jù) @property(readonly, nullable) CMAccelerometerData *accelerometerData; // 開始更新加速計(jì)數(shù)據(jù),不帶回調(diào),可以添加定時(shí)器定時(shí)去獲取CMMotionManager對(duì)象的accelerometerData數(shù)據(jù) - (void)startAccelerometerUpdates; // 開始更新加速計(jì)數(shù)據(jù),帶回調(diào),由于數(shù)據(jù)可能更新頻率快,不建議使用主隊(duì)列 - (void)startAccelerometerUpdatesToQueue:(NSOperationQueue *)queue withHandler:(CMAccelerometerHandler)handler; // 不再需要更新數(shù)據(jù)的時(shí)候需要調(diào)用停止更新的方法 - (void)stopAccelerometerUpdates;
-
使用步驟
// 1. 初始化CMMotionManager對(duì)象并設(shè)置屬性存儲(chǔ),設(shè)置數(shù)據(jù)的更新間隔
- (CMMotionManager *)motionManage {
if (!_motionManage) {
_motionManage = [[CMMotionManager alloc] init];
// 控制傳感器的更新間隔
_motionManage.accelerometerUpdateInterval = 0.2;
_motionManage.gyroUpdateInterval = 0.2;
_motionManage.magnetometerUpdateInterval = 0.2;
}
return _motionManage;
}// 2. 開始/結(jié)束更新數(shù)據(jù),只舉例帶回調(diào)的方法 - (IBAction)accelerometerTest:(UIButton *)sender { BOOL start = !sender.selected; // 2.1 根據(jù)設(shè)置的時(shí)間間隔定期更新數(shù)據(jù) if (start) { // 可用性檢測 if(![self.motionManage isAccelerometerAvailable]){ [self showWithTitle:@"加速計(jì)不可用" message:nil]; return; } sender.selected = YES; __weak typeof (self) weakSelf = self; // 數(shù)據(jù)更新有可能比較頻繁,不建議使用主隊(duì)列 NSOperationQueue *queue = [NSOperationQueue new]; [self.motionManage startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) { // 回到主線程 dispatch_async(dispatch_get_main_queue(), ^{ // 數(shù)據(jù)顯示 weakSelf.accelerationXLbl.text = [NSString stringWithFormat:@"%.2f", accelerometerData.acceleration.x]; weakSelf.accelerationYLbl.text = [NSString stringWithFormat:@"%.2f", accelerometerData.acceleration.y]; weakSelf.accelerationZLbl.text = [NSString stringWithFormat:@"%.2f", accelerometerData.acceleration.z]; }); }]; } else { // 2.2 停止獲取數(shù)據(jù) sender.selected = NO; [self.motionManage stopAccelerometerUpdates]; } }
-
自帶搖一搖功能
UIResponder類中已經(jīng)封裝好了搖一搖功能,當(dāng)對(duì)象成為第一響應(yīng)者之后,
系統(tǒng)就會(huì)通知對(duì)象搖一搖的開始/結(jié)束狀態(tài),實(shí)現(xiàn)以下方法就可以- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event; - (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event; - (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event;