iBeacon的第一篇(基于Swift实现)
低功耗蓝牙技术现在几乎是只能手机的标配。随着这一技术的发展,苹果在2013年WWDC大会上,苹果推出iBeacon技术。该技术允许开发人员开发能够使用iBeacon硬件传感器的iOS应用程序,来为相应的应用程序提供更加精准的位置信息。2014年WWDC大会上,苹果表示,对iBeacon技术进行了改善,借助该技术,应用程序现在能够跟踪到用户所在的楼层的精确位置信息。
iBeacon的工作方式是Transmitter-Receiver,即基站-接收机模式的。基站?这个时候不要想到移动、联通的那些大铁塔。这个基站可以是一个运行着Bluetooth 4.0 LE的设备,也可以是经过配置的iPhone、iPad。iPhone4S和之后的iPhone、iPad3或之后的iPad,包括iPad mini都可以配置成iBeacon基站。
这里列举个iBeacon的使用场景:在房屋中介中使用。米国一家技术公司把 iBeacon 安装在要出售的房屋前,当用户开车至此,不用下车就可以用中介的 APP 获得此房屋所有相关信息和照片,不用打印及搜索。据说,效果还是很不错的,大约有一半左右的用户打开手机查看了相关信息。甚至于还有一个老外用iBeacon做了一个实景的经营游戏,点击这里观看。
一个基站主要有三部分标识:
1. UUID,形如:206A2476-D4DB-42F0-BF73-030236F2C756。用来标识某一个公司。比如,某个房地产公司的的全部的基站都用同一个UUID。
2. major,用来标识某一类的beacon。比如这个房地产公司的北京的房子都设定为1,上海的都设定为2。
3. minor,用来标识某一个特定的beacon。比如某栋楼的某个基站。
用这个房地产商开发的app就可以获取到UUID,和后面的major值和minor值。当app进入beacon的区域,探测到UUID:”206A2476-D4DB-42F0-BF73-030236F2C756“,major为1,minor为20。那么就表明这个用户在这个房地产商的北京楼盘的编号20的楼盘。这栋楼的说明文字、图片或者视频等就可以展现在用户面前。
下面就进入我们的教程。不过前提是你要是苹果的开发者,因为这个app需要在真机运行。然后要有2台设备,一个当基站,一个当接收机。最后,还得有一台能运行Xcode的电脑。
首先创建一个SingleView的项目,起个名字叫MyBeacon。然后把我们需要用到的Framework加进来,把CoreBluethooth.framework和CoreLocation.framework加到项目中。
创建一个Storybard,并在视图中添加如下的UIController:
(这是Xcode6创建的storyboard)
关于storyboard的细节就不多说了。下面新建两个文件:TrackViewController和ConfigViewController。这时候会自动生成一个ViewControler类。分别把他们按照上图所示对应到stroyboard的几个ViewController中,并添加图中所示的UILabel。然后把需要现实APP运行结果的UILabel作为IBOutlet。
TrackViewController的IBOutlet是这样的:(以下的两个文件都用的是Swift语言,如果你需要的话可以自行修改为OC语言)
ConfigViewController的IBOutlet是这样的:
先搞定基站部分
在ConfigViewController中添加如下的属性:
第一个CLBeaconRegin属性,用来设置基站需要的proximityUUID,major和minor的给你信息。
第二个NSDictionary的属性,用来获取外设的数据。
第三个CBPeripheralManager的属性,用来开启基站的数据传输。
这些属性后面的感叹号是Swift里的一种语法。变量或者属性的定义,在类型的后面可以是问号后者是感叹号。他们都表示这个变量或者属性可以是有值的也可以是空值nil(注意这里的nil和OC里的nil是两回事)。但是使用问号的变量或者属性需要在使用前判断是否有值,如果有的话通过感叹号取值。使用感叹号定义的变量或者属性则可以直接取值并使用。这里使用感叹号定义属性,这样在使用的时候可以直接取值。详细的使用会在代码中体现。
要让iOS设备开始传输信号还需要做些其他的事情。在viewDidLoad中调用几个方法才能让基站工作起来。首先调用的是initBeacon方法,之后是要把数据现实出来的setLabels方法。
在这里通过uuid字符串设定NSUUID实例。uuid字符串可以通过uuidgen命令在终端里生成。也可以通过代码生成,但是这里没有这个必要。然后其他的值major和minor分别设定为1,identifier设定为我们示例的名字。
下面我们就要考虑传输的问题了。在用户点击了传输按钮之后开始使用设备的蓝牙外设发出信号。方法如下:
在这个方法里,首先通过我们设定的beaconRegion属性获得相关的外设数据。这个数据是NSDictionary类型的。需要说明的是,这个类型可以在Swift中使用,但是和Swift本身的Dictionary是不兼容的。
之后,初始化了CBPeripheralManager属性。这里简单处理,设定queue和options为nil。为了设定代理为self需要实现CBPeripheralManagerDelegate protocol。
实现了这个protocol之后,还需要实现这个protocol的一个方法。这个方法在OC里来讲是required的,所以必须实现。
外设的状态中有两个需要我们处理的。一个是PoweredOn一个PoweredOff。On了就让peripheralManager开始对外发出信号。Off了就停止发出信号。所以呢,如果直接在transmitAction中调用代码startAdvertising是设备对外发出信号的话,会报错。Console会打印出”CBPeripheralManager is not Powered on“。所以我们在以上的代码中在Pwoered On的时候再开始调用代码发出信号。发出信号的时候呢,我们给了advertising方法传入了从beaconRegion取出来的NSDictionary数据。这也是接收机辨识基站用到的数据。
之后我们把数据显示在界面上。
这个方法很简单,只要在viewDidLoad里调用setLabels方法就可以。全部设定在基站里的数据都会显示在界面上了。其中包括uuid、major和minor,最后是我们设定的identifier。用Swift比用OC写代码是会少一些量,如果你对Swift足够熟的话。比如,取得一个值的字符窜值的时候就不用[NSString stringWithFormat:"..."]这么麻烦的写法了。
我们的app已经可以作为基站使用了。但是没有接收机的话这个app可是一点都不好玩。在下面就开始处理接收机的部分。
接受iBeacon信息
接收iBeacon信号的底层功能已经在Core Location Framework里实现了。在iOS7里,可以自动识别你是否进入了一个区域以及其他的距离之类的信息。
下面在TrackViewController中处理,首先把CoreLocation库的头文件加进来。然后增加下面的两个属性:
CLBeaconRegion属性是用来定义我们要寻找的beacon的。这个app只会接收到有同样的UUID的的发射机发射的信号。
CLLocationManager属性是用来建立位置服务并搜索beacon的。
在viewDidLoad方法中添加如下代码:
首先,要初始化CLLocationManager,然后把代理设置为self。当然设定代理之前我么需要实现CLLocationManagerDelegate protocol,具体的就不写出来了。然后调用initRegion方法,这个会在后面给出详情。
首先创建UUID实例。用来初始化这个实例的的uuid字符串必须和基站的uuid字符串是一样的,要不互相找不见。
然后初始化beaconRegion,proximityUUID就是前面创建的UUID实例,identitifer就是在基站中用到的identifier字符串。然后开始监测前面初始化出来的beaconRegion。调用代码self.trackLocationManager.startMonitoringForRegion(self.beaconRegion)开始检测。
接下来,我们需要监测这个app进入和离开区域的事件,代码如下:
第一个方法是进入的,当你接收到基站信号的时候这个方法就开始执行(当然你要离基站足够近)。然后调用locationManager的startRagingBeaconsInRegion方法,病传入我们之前定义好的beaconRegion属性。
第二个方法很简单,就是在离开这个区域的时候就停止执行,调用locationManager的stopRagingBeaconsInRegion。
下面是didRangeBeacons方法:
首先判断方法传入的beacons数组的元素有多少。如果有0个的时候什么都不做。如果有一个或以上的话,这里简单处理只取最后一个beacon来处理。
之后的代码都只是从beacon中取出数据来现实在界面上,比如UUID,major,minor,accuracy和RSSI。这些值会随着接收机和基站的距离不同以及基站的设置不同而一直改变。尤其accuracy和RSSI都是beacon用来现实距离的。其中proximity会显示四个值:Unknown、Immediate、Near和Far。Immediate是半米以内,Near远一些,Far更远。RSSI是信号强度。
运行程序
现在这个app可以运行了。但是需要分别运行在两台可以兼容低功耗蓝牙的设备上。一台当基站一台当接收机。
当你从足够远(10~20米)的地方想基站的方向走的时候,一个你”didEnterRange”那个didRangeBeacons方法就会被调用,这是就可以从方法中传入的beacon数组中取出值来现实在界面上。
但是有一点需要注意的是,我们处理的基站只有一个,所以每次都取出beacon数组的最后一个。如果有多个beacon基站的话就需要循环beacon数组的每一个元素,判断这个元素的proximity是Immdiate还是Near还是Far等。
每次测试的时候都进入或者离开一个区域太麻烦。可以在viewDidLoad方法中添加一行代码:
然后添加如下方法:
手动调用了didStartMonitoringForRegion,在这个方法中调用了trackLocationManager的startRangingBeaconsInRegion方法。这样虽然不完美,但是足够测试了。
下面是代码列表:
ConfigViewController:
import UIKit
import CoreLocation
import CoreBluetooth class ConfigViewController: UIViewController, CBPeripheralManagerDelegate { // properties
@IBOutlet var uuidLabel: UILabel
@IBOutlet var majorLabel: UILabel
@IBOutlet var minorLabel: UILabel
@IBOutlet var identityLabel: UILabel
@IBOutlet var transmitButton: UIButton var beaconRegion : CLBeaconRegion!
var beaconPeripheralData : NSDictionary!
var peripheralManager : CBPeripheralManager! override func viewDidLoad() {
super.viewDidLoad() self.initBeacon()
self.setLabels()
} func initBeacon(){
self.beaconRegion = CLBeaconRegion(proximityUUID: NSUUID(UUIDString: "206A2476-D4DB-42F0-BF73-030236F2C756")
, major: , minor: , identifier: "com.mybeacon.region")
} func setLabels(){
self.uuidLabel.text = self.beaconRegion.proximityUUID.UUIDString
self.majorLabel.text = self.beaconRegion.major.stringValue
self.minorLabel.text = self.beaconRegion.minor.stringValue
self.identityLabel.text = self.beaconRegion.identifier
} override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
} @IBAction func transmitAction(sender: UIButton) {
self.beaconPeripheralData = self.beaconRegion.peripheralDataWithMeasuredPower(nil)
self.peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
} // delegate methods
func peripheralManagerDidUpdateState(peripheral: CBPeripheralManager!){
if peripheral.state == CBPeripheralManagerState.PoweredOn {
println("Powered on")
self.peripheralManager.startAdvertising(self.beaconPeripheralData)
}
else if peripheral.state == CBPeripheralManagerState.PoweredOff {
println("Powered off")
self.peripheralManager.stopAdvertising()
}
}
}
TrackViewController:
import UIKit
import CoreLocation class TrackViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet var beaconLabel: UILabel
@IBOutlet var uuidLabel: UILabel
@IBOutlet var majorLabel: UILabel
@IBOutlet var minorLabel: UILabel
@IBOutlet var accuracyLabel: UILabel
@IBOutlet var distanceLabel: UILabel
@IBOutlet var rssiLabel: UILabel var beaconRegion : CLBeaconRegion!
var trackLocationManager : CLLocationManager! override func viewDidLoad() {
super.viewDidLoad() self.trackLocationManager = CLLocationManager();
self.trackLocationManager.delegate = self;
self.initRegion()
self.locationManager(self.trackLocationManager, didStartMonitoringForRegion: self.beaconRegion)
} func initRegion(){
var uuid = NSUUID(UUIDString: "206A2476-D4DB-42F0-BF73-030236F2C756")
self.beaconRegion = CLBeaconRegion(proximityUUID: uuid, identifier: "com.mybeacon.region")
self.trackLocationManager.startMonitoringForRegion(self.beaconRegion)
} func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
self.trackLocationManager.startRangingBeaconsInRegion(self.beaconRegion)
} override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
} func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
self.trackLocationManager.startRangingBeaconsInRegion(self.beaconRegion)
} func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
self.trackLocationManager.stopRangingBeaconsInRegion(self.beaconRegion)
self.beaconLabel.text = "No"
} func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [AnyObject]!, inRegion region: CLBeaconRegion!) {
println("beacons count" + String(beacons.count))
if beacons.count <= {
return
} var beacon: AnyObject = beacons[beacons.count - ] self.beaconLabel.text = "Yes"
self.uuidLabel.text = beacon.proximityUUID ? beacon.proximityUUID!.UUIDString : ""
self.majorLabel.text = beacon.major ? beacon.major!.stringValue : ""
self.minorLabel.text = beacon.minor ? beacon.minor!.stringValue : ""
self.accuracyLabel.text = beacon.accuracy ? String(beacon.accuracy) : ""
if beacon.proximity { switch(beacon.proximity!){
case .Unknown:
self.distanceLabel.text = "Unknown proximity"
case CLProximity.Immediate:
self.distanceLabel.text = "Immediate"
case CLProximity.Near:
self.distanceLabel.text = "Near"
case CLProximity.Far:
self.distanceLabel.text = "Far"
default:
println("default of switch-case")
}
}
self.rssiLabel.text = beacon.rssi ? beacon.rssi!.description : ""
}
80 }
转载请注明出处!
iBeacon的第一篇(基于Swift实现)的更多相关文章
- 实践torch.fx第一篇——基于Pytorch的模型优化量化神器
第一篇--什么是torch.fx 今天聊一下比较重要的torch.fx,也趁着这次机会把之前的torch.fx笔记整理下,笔记大概拆成三份,分别对应三篇: 什么是torch.fx 基于torch.fx ...
- 网络编程[第一篇]基于tcp协议的套接字编程
将服务端-客户端的连接比作双方打电话的过程 2019-07-24 一.客户端 主动的一方: 客户端实例化一个socket对象--> 主动像服务端发送连接请求--> (服务端接受请求后即可进 ...
- ImageJ 学习第一篇
ImageJ是世界上最快的纯Java的图像处理程序.它可以过滤一个2048x2048的图像在0.1秒内(*).这是每秒40万像素!ImageJ的扩展通过使用内置的文本编辑器和Java编译器的Image ...
- 深入研究C语言 第一篇(续)
没有读过第一篇的读者,可以点击这里,阅读深入研究C语言的第一篇. 问题一:如何打印变量的地址? 我们用取地址符&,可以取到变量的偏移地址,用DS可以取到变量的段地址. 1.全局变量: 我们看到 ...
- .net开发笔记(十三) Winform常用开发模式第一篇
上一篇博客最后我提到“异步编程模型”(APM),之后本来打算整理一下这方面的材料然后总结一下写篇文章与诸位分享,后来在整理的过程中不断的延伸不断地扩展,发现完全偏离了“异步编程”这个概念,前前后后所有 ...
- 深入理解javascript函数系列第一篇——函数概述
× 目录 [1]定义 [2]返回值 [3]调用 前面的话 函数对任何一门语言来说都是一个核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即 ...
- 第一篇:SOUI是什么?
概述 用C++做产品最痛苦的是什么?肯定是做UI. SOUI的使命就是把痛苦的UI变化成快乐的UI. 什么?UI还能快乐?脑子进水了吗? 当你看完这个系统教程的时候相信你面对UI至少不会再痛苦.你可以 ...
- [译]PrestaShop开发者指南 第一篇 基础
# 第一篇 基础 PS(PrestaShop简称)一开始就设定了能够在它的基础上很简单的构建第三方模块的机制,让它成为一款具有极高定制性的电子商务软件. PS的可以在三个方面进行定制: * 主题 * ...
- [转载] Android Metro风格的Launcher开发系列第一篇
前言:从毕业到现在已经三年多了,回忆一下这三年基本上没有写过博客,总是觉得忙,没时间写,也觉得写博客没什么大用.但是看到很多大牛们都在写博客,分享自己的东西,所以嘛本着向大牛看齐,分享第一,记录第二的 ...
随机推荐
- 在虚拟机里新建一个20G的硬盘,如何把他挂载在 /work 目录上
目的:在虚拟机里新建一个20G的硬盘,然后想把他挂载在 /work 目录上 /dev/sda (系统盘) /dev/sdb (数据盘) /dev/sdc (数据盘) /dev/sdd (数据盘) /d ...
- SpringMVC传统风格控制器和基于注解的控制器
SpringMVC的DispatcherServlet 之前说过springMVC是使用Servlet作为控制器,就是这个用于调度的DispatcherServlet了.这个是servlet,可以根据 ...
- 【BZOJ】1001: [BeiJing2006]狼抓兔子(最小割 / 对偶图)
题目 传送门:QWQ 分析 显然答案是最小割. 然后dinic卡一卡过去了. 其实是懒得写转对偶图:正解 (dinic原来写的是vector,后来改的比较鬼畜 代码 #include <bits ...
- PowerMock单元测试
在Java程序的单元测试中常用的mock工具有Mockito和EasyMock.但是这两种mock工具都无法实现对静态.final.私有方法或类的mock.因此有了功能强大的PowerMock工具.P ...
- EntityFramework 更新数据库字段的三种方法
例: 实体类: public class TestDbContext : DbContext { public DbSet<Test> Tests { get; set; } public ...
- 0CTF题中的神奇宝贝WP
前言: 今天被尘少拉去做题 然后做到一个脑洞打开的题..... 神奇宝贝!?!? 正文: 一开始来到这=-= 弱密码猜测:admin admin 验证码用calc去算 然后成功进入 第一次去做神奇宝贝 ...
- centos7.3查看时区
[root@iZ2ze3gf6h0zndx5dxyhqiZ ~]# date -R Fri, Apr :: + 即可查看时区 我们国家的东八区(+0800) 查看clock系统配置文件 [root@i ...
- 根据不同分辨率载入相应CSS样式表
index.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http:// ...
- TCP/IP协议详解之广播和多播
广播和多播仅应用于 U D P,它们对需将报文同时传往多个接收者的应用来说十分重要.T C P是一个面向连接的协议,它意味着分别运行于两主机(由 I P地址确定)内的两进程(由端口号确定)间存在一条连 ...
- NetworkStream.write只能使用一次,后面再使用无效
public delegate void ShowMessageMethod2(TcpClient tc); private void ShowMessage2(TcpClient tc) { Str ...