● 只能用于同一个应用程序之间的连接
/*
关于蓝牙的数据传输
1. 一次性传送,没有中间方法,所谓中间方法值得是,传输进度比例
对于用户而言,选择了传输,就需要等待传输完成,或者传输以失败告终
这就意味着,在实际开发过程中,最好不要用蓝牙传输太大的文件
在实际应用中,蓝牙通常用于传递游戏数据模型,用于联机对战,譬如点对点的棋牌类游戏。
*/
● GameKit.framework(用法简单)
• 只能用于iOS设备之间的连接,多用于游戏(比如五子棋对战),从iOS7开始过期
● MultipeerConnectivity.framework
• 只能用于iOS设备之间的连接,从iOS7开始引入,主要用于文件共享(仅限于沙盒的文
件)
● ExternalAccessory.framework
• 可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内较少)
● CoreBluetooth.framework(时下热门)
• 可用于第三方蓝牙设备交互,必须要支持蓝牙4.0
● 硬件至少是4s,系统至少是iOS6
● 蓝牙4.0以低功耗著称,一般也叫BLE(Bluetooth Low Energy)
• 目前应用比较多的案例:运动手坏、嵌入式设备、智能家居
GKPeerPickerController *ppc = [[GKPeerPickerController alloc] init]; ppc.delegate = self;
[ppc show];
● 在代理方法中监控蓝牙的连接
- (void)peerPickerController:(GKPeerPickerController *)picker
didConnectPeer:(NSString *)peerID toSession:(GKSession *)session {
NSLog(@"连接到设备:%@", peerID);
// 关闭蓝牙设备显示界面
[picker dismiss];
// 设置接收到蓝牙数据后的监听器
[session setDataReceiveHandler:self withContext:nil];
// 保存session
self.session = session;
}
- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer
inSession: (GKSession *)session context:(void *)context {
}
● 利用GKSession给其他设备发送数据 ● 给指定的连接设备发送数据
- (BOOL)sendData:(NSData *) data toPeers:(NSArray *)peers
withDataMode:(GKSendDataMode)mode error:(NSError **)error;
● 给所有连接的设备发送数据
- (BOOL)sendDataToAllPeers:(NSData *) data withDataMode:
(GKSendDataMode)mode error:(NSError **)error;
实例1:
#import "ViewController.h" #include <GameKit/GameKit.h> @interface ViewController ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate, GKPeerPickerControllerDelegate> /** * 连接 */ - (IBAction)connect; /** * 选择图片 */ - (IBAction)selectedPhoto; /** * 发送 */ - (IBAction)send; @property (weak, nonatomic) IBOutlet UIImageView *customIV; /** * 会话 */ @property (nonatomic, strong) GKSession *session; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (IBAction)connect { // 1.创建选择其他蓝牙设备的控制器 GKPeerPickerController *peerPk = [[GKPeerPickerController alloc] init]; // 2.成为该控制器的代理 peerPk.delegate = self; // 3.显示蓝牙控制器 [peerPk show]; } #pragma mark - GKPeerPickerControllerDelegate // 4.实现dialing方法 /** * 当蓝牙设备连接成功就会调用 * * @param picker 触发时间的控制器 * @param peerID 连接蓝牙设备的ID * @param session 连接蓝牙的会话(可用通讯), 以后只要拿到session就可以传输数据 */ - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session { NSLog(@"%@", peerID); // 1.保存会话 self.session = session; // 2.设置监听接收传递过来的数据 /* Handler: 谁来处理接收到得数据 withContext: 传递数据 */ [self.session setDataReceiveHandler:self withContext:nil]; // 2.关闭显示蓝牙设备控制器 [picker dismiss]; } /** * 接收到其它设备传递过来的数据就会调用 * * @param data 传递过来的数据 * @param peer 传递数据设备的ID * @param session 会话 * @param context 注册监听时传递的数据 */ - (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context { // NSLog(@"%s", __func__); // 1.将传递过来的数据转换为图片(注意: 因为发送的时图片, 所以才需要转换为图片) UIImage *image = [UIImage imageWithData:data]; self.customIV.image = image; } - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker { } - (IBAction)send { // 利用session发送图片数据即可 // 1.取出customImageView上得图片, 转换为二进制 UIImage *image = self.customIV.image; NSData *data = UIImagePNGRepresentation(image); /* GKSendDataReliable, 数据安全的发送模式, 慢 GKSendDataUnreliable, 数据不安全的发送模式, 快 */ /* data: 需要发送的数据 DataReliable: 是否安全的发送数据(发送数据的模式) error: 是否监听发送错误 */ [self.session sendDataToAllPeers:data withDataMode:GKSendDataReliable error:nil]; } - (IBAction)selectedPhoto { // 1.创建图片选择控制器 UIImagePickerController *imagePk = [[UIImagePickerController alloc] init]; // 2.判断图库是否可用打开 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) { // 3.设置打开图库的类型 imagePk.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; imagePk.delegate = self; // 4.打开图片选择控制器 [self presentViewController:imagePk animated:YES completion:nil]; } } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { // NSLog(@"%@", info); self.customIV.image = info[UIImagePickerControllerOriginalImage]; [picker dismissViewControllerAnimated:YES completion:nil]; } @end
实例2:
/* > 讲解相片选择器 *先不设置代理,创建后直接运行 *从选择相片后需要关闭控制器引出代理 *实现代理方法展示数据 *PPT讲解蓝牙连接(不是所有应用都可以用蓝牙传输东西,必须相同应用事先写好如何处理. 苹果蓝牙传输非常满, 而且传输过程中没有进度) *在storyboard中添加按钮, 点击按钮后创建对端选择控制器, 设置代理,显示视图控制器 *在代理方法中打印peerID, 讲解session用途查看头文件引出利用传递数据 *定义属性保存session , 在storyboard中添加按钮监听按钮点击利用session传递图片 *讲解传递数据方法两种模式区别 *设置数据处理者, 讲解如何找到数据处理方法,在数据处理方法中打印LOG运行验证 *将接收到的数据转换为图片后现实在界面上 *总结蓝牙传输 */ #import "CZViewController.h" #import <GameKit/GameKit.h> @interface CZViewController ()<UIImagePickerControllerDelegate, UINavigationControllerDelegate, GKPeerPickerControllerDelegate> /** * 现实相片 */ - (IBAction)selectPhoto; @property (weak, nonatomic) IBOutlet UIImageView *imageView; /** * 蓝牙连接 */ - (IBAction)connectBtnClick; /** * 会话对象 */ @property (nonatomic, strong)GKSession *session; /** * 发送相片 */ - (IBAction)sendPhoto; @end @implementation CZViewController #pragma mark 选择照片 /* 照片源类型 UIImagePickerControllerSourceTypeCamera 照相机 UIImagePickerControllerSourceTypePhotoLibrary 照片库(通过同步存放的,用户不能删除) UIImagePickerControllerSourceTypeSavedPhotosAlbum 保存的照片(通过拍照或者截屏保存的,用户可以删除) */ - (IBAction)selectPhoto { // 1.判断照片源是否可用 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { // 1.1实例化控制器 UIImagePickerController *picker = [[UIImagePickerController alloc] init]; // 1.2设置照片源 [picker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; // 1.3设置允许修改 picker.allowsEditing = YES; // 1.4设置代理 picker.delegate = self; // 1.5显示控制器 [self presentViewController:picker animated:YES completion:^{ }]; }else { // 2.照片源不可用 NSLog(@"照片源不可用"); } } #pragma mark - imagePicker代理方法 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { // NSLog(@"%@", info); // 获取相片 UIImage *image = info[@"UIImagePickerControllerEditedImage"]; // 设置相片 self.imageView.image = image; // 关闭相片选择器 [self dismissViewControllerAnimated:YES completion:^{ }]; } #pragma mark - 蓝牙连接 - (IBAction)connectBtnClick { // 1.创建对端选择控制器 GKPeerPickerController *picker = [[GKPeerPickerController alloc] init]; // 2.设置代理 picker.delegate = self; // 3.显示试图控制器 [picker show]; } #pragma mark - GKPeerPickerControllerDelegate // 完成对端连接 // GKSession对象用于表现两个蓝牙设备之间连接的一个会话,你也可以使用它在两个设备之间发送和接收数据。 - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session { NSLog(@"连接成功 %@", peerID); // 保存会话 self.session = session; // 设置数据处理对象,类似于delegate [self.session setDataReceiveHandler:self withContext:nil]; // 关闭对端选择控制器 [picker dismiss]; } // 取消对端选择控制器 - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker { NSLog(@"取消蓝牙选择器"); } // 发送相片 - (IBAction)sendPhoto { NSData *imageData = UIImagePNGRepresentation(self.imageView.image); // 利用session发送相片 // self.session sendData:<#(NSData *)#> toPeers:<#(NSArray *)#> withDataMode:<#(GKSendDataMode)#> error:<#(NSError *__autoreleasing *)#> /* TCP协议、UDP协议 1. 要发送的数据(二进制的) 2. 数据发送模式 GKSendDataReliable :确保数据发送成功(TCP协议,对网络压力大) GKSendDataUnReliable :只管发送不管成功(UDP协议,对数据完整性要求不高,对网络压力下) */ [self.session sendDataToAllPeers:imageData withDataMode:GKSendDataReliable error:nil]; } // 数据接受处理方法,此方法需要从文档中粘贴出来,没有智能提示 - (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context { NSLog(@"接收到数据"); // 将NSData转换成UIImage UIImage *image = [UIImage imageWithData:data]; self.imageView.image = image; } /* 关于蓝牙的数据传输 1. 一次性传送,没有中间方法,所谓中间方法值得是,传输进度比例 对于用户而言,选择了传输,就需要等待传输完成,或者传输以失败告终 这就意味着,在实际开发过程中,最好不要用蓝牙传输太大的文件 在实际应用中,蓝牙通常用于传递游戏数据模型,用于联机对战,譬如点对点的棋牌类游戏。 */ @end
● Core Bluetooth测试比较麻烦,正常情况下,得至少有2台真实的蓝牙4.0设备
● 如何让iOS模拟器也能测试蓝牙4.0程序?
● 买一个CSR蓝牙4.0 USB适配器,插在Mac上
● 在终端输入sudo nvram bluetoothHostControllerSwitchBehavior="never"
● 重启Mac
● 用Xcode 4.6调试代码,将程序跑在iOS 6.1的模拟器上 (苹果把iOS 7.0模拟器对BLE的支持移除掉了)
● Core Bluetooth的使用场景
● 运动手环、智能家居、嵌入式设备等等(金融刷卡器、心电测量器)
● 每个蓝牙4.0设备都是通过服务(Service)和特征(Characteristic)来展示自己 的
● 一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征 ● 特征是与外界交互的最小单位
• 比如说,一台蓝牙4.0设备,用特征A来描述自己的出厂信息,用特征B来收发 数据
● 服务和特征都是用UUID来唯一标识的,通过UUID就能区别不同的服务和特征 ● 设备里面各个服务(service)和特征(characteristic)的功能,均由蓝牙设备硬件厂
商提供,比如哪些是用来交互(读写),哪些可获取模块信息(只读)等
● 建立中心设备
● 扫描外设(Discover Peripheral)
● 连接外设(Connect Peripheral)
● 扫描外设中的服务和特征(Discover Services And Characteristics)
● 利用特征与外设做数据交互(Explore And Interact)
● 断开连接(Disconnect)
#import "ViewController.h" #import <CoreBluetooth/CoreBluetooth.h> @interface ViewController ()<CBCentralManagerDelegate, CBPeripheralDelegate> /** * 外设 */ @property (nonatomic, strong) NSMutableArray *peripherals; /** * 中心管理者 */ @property (nonatomic, strong) CBCentralManager *mgr; @end @implementation ViewController - (NSMutableArray *)peripherals { if (!_peripherals) { _peripherals = [NSMutableArray array]; } return _peripherals; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 1.创建中心设备 CBCentralManager *mgr = [[CBCentralManager alloc] init]; self.mgr = mgr; // 设置代理 mgr.delegate = self; // 2.利用中心设备扫描外部设备 /* 如果指定数组代表只扫描指定的设备 */ [mgr scanForPeripheralsWithServices:nil options:nil]; } #pragma mark - CBCentralManagerDelegate - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { // 保存扫描到得外部设备 // 判断如果数组中不包含当前扫描到得外部设置才保存 if (![self.peripherals containsObject:peripheral]) { peripheral.delegate = self; [self.peripherals addObject:peripheral]; } } /** * 模拟点击, 然后连接所有的外设 */ - (void)start { for (CBPeripheral *peripheral in self.peripherals) { /** * 连接外设 */ [self.mgr connectPeripheral:peripheral options:nil]; } } /** * 连接外设成功调用 */ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { // 扫描外设中得服务 [peripheral discoverServices:nil]; } /** * 连接外设失败调用 */ - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { } #pragma makr - CBPeripheralDelegate /** * 只要扫描到服务就会调用 * * @param peripheral 服务所在的外设 */ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { // 获取外设中所有扫描到得服务 NSArray *services = peripheral.services; for (CBService *service in services) { // 拿到需要的服务 if ([service.UUID.UUIDString isEqualToString:@"123"]) { // 从需要的服务中查找需要的特征 // 从peripheral中得service中扫描特征 [peripheral discoverCharacteristics:nil forService:service]; } } } /** * 只要扫描到特征就会调用 * * @param peripheral 特征所属的外设 * @param service 特征所属的服务 */ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { // 拿到服务中所有的特诊 NSArray *characteristics = service.characteristics; // 遍历特征, 拿到需要的特征处理 for (CBCharacteristic * characteristic in characteristics) { if ([characteristic.UUID.UUIDString isEqualToString:@"8888"]) { NSLog(@"设置闹钟"); } } } @end
● 绝大多数智能手机支持蓝牙 4.0(BLE)
● 蓝牙芯片发展迅速,在性能和效率方面都有很大提高,且不断变得更小更便宜
● iBeacon + 蓝牙,前景一片光明
● 应用之一:室内导航
● Estimote公司为iBeacon提供基站
● 3个iBeacon基站的预购价格为99美元(约合人民币610元)
● Estimote公司推出的iBeacon基站的最远传输距离为50m,但是他们推荐在10m 范围内的使用效果最好
● 一块纽扣电池就能为一个iBeacon基站提供长达 2 年的使用寿命,而且是在设 备不断对外发射信号的情况下