iBeacon的第一篇:基于Swift實現(xiàn)
低功耗藍牙技術(shù)現(xiàn)在幾乎是智能手機的標配。隨著這一技術(shù)的發(fā)展,蘋果在2013年WWDC大會上,蘋果推出iBeacon技術(shù)。該技術(shù)允許開發(fā) 人員開發(fā)能夠使用iBeacon硬件傳感器的iOS應用程序,來為相應的應用程序提供更加精準的位置信息。2014年WWDC大會上,蘋果表示,對 iBeacon技術(shù)進行了改善,借助該技術(shù),應用程序現(xiàn)在能夠跟蹤到用戶所在的樓層的精確位置信息。
iBeacon的工作方式是Transmitter-Receiver,即基站-接收機模式的?;荆窟@個時候不要想到移動、聯(lián)通的那些大鐵 塔。這個基站可以是一個運行著Bluetooth 4.0 LE的設備,也可以是經(jīng)過配置的iPhone、iPad。iPhone4S和之后的iPhone、iPad3或之后的iPad,包括iPad mini都可以配置成iBeacon基站。
這里列舉個iBeacon的使用場景:在房屋中介中使用。米國一家技術(shù)公司把 iBeacon 安裝在要出售的房屋前,當用戶開車至此,不用下車就可以用中介的 APP 獲得此房屋所有相關(guān)信息和照片,不用打印及搜索。據(jù)說,效果還是很不錯的,大約有一半左右的用戶打開手機查看了相關(guān)信息。甚至于還有一個老外用 iBeacon做了一個實景的經(jīng)營游戲,點擊這里觀看。
一個基站主要有三部分標識:
1. UUID,形如:206A2476-D4DB-42F0-BF73-030236F2C756。用來標識某一個公司。比如,某個房地產(chǎn)公司的全部的基站都用同一個UUID。
2. major,用來標識某一類的beacon。比如這個房地產(chǎn)公司的北京的房子都設定為1,上海的都設定為2。
3. minor,用來標識某一個特定的beacon。比如某棟樓的某個基站。
用這個房地產(chǎn)商開發(fā)的app就可以獲取到UUID,和后面的major值和minor值。當app進入beacon的區(qū)域,探測到 UUID:”206A2476-D4DB-42F0-BF73-030236F2C756“,major為1,minor為20。那么就表明這個用戶在這 個房地產(chǎn)商的北京樓盤的編號20的樓盤。這棟樓的說明文字、圖片或者視頻等就可以展現(xiàn)在用戶面前。
下面就進入我們的教程。不過前提是你要是蘋果的開發(fā)者,因為這個app需要在真機運行。然后要有2臺設備,一個當基站,一個當接收機。最后,還得有一臺能運行Xcode的電腦。
首先創(chuàng)建一個SingleView的項目,起個名字叫MyBeacon。然后把我們需要用到的Framework加進來,把CoreBluethooth.framework和CoreLocation.framework加到項目中。
創(chuàng)建一個Storybard,并在視圖中添加如下的UIController:
關(guān)于storyboard的細節(jié)就不多說了。下面新建兩個文件:TrackViewController 和 ConfigViewController 。這時候會自動生成一個 ViewControler 類。分別把他們按照上圖所示對應到 stroyboard 的幾個ViewController 中,并添加圖中所示的 UILabel 。然后把需要現(xiàn)實APP運行結(jié)果的 UILabel 作為 IBOutlet 。
TrackViewController 的 IBOutlet 是這樣的:(以下的兩個文件都用的是Swift語言,如果你需要的話可以自行修改為OC語言)
ConfigViewController 的 IBOutlet 是這樣的:
先搞定基站部分
在 ConfigViewController 中添加如下的屬性:
第一個 CLBeaconRegin 屬性,用來設置基站需要的 proximityUUID ,major 和 minor 的給你信息。
第二個 NSDictionary 的屬性,用來獲取外設的數(shù)據(jù)。
第三個 CBPeripheralManager 的屬性,用來開啟基站的數(shù)據(jù)傳輸。
這些屬性后面的感嘆號是Swift里的一種語法。變量或者屬性的定義,在類型的后面可以是問號后者是感嘆號。他們都表示這個變量或者屬性可以是 有值的也可以是空值nil(注意這里的nil和OC里的nil是兩回事)。但是使用問號的變量或者屬性需要在使用前判斷是否有值,如果有的話通過感嘆號取 值。使用感嘆號定義的變量或者屬性則可以直接取值并使用。這里使用感嘆號定義屬性,這樣在使用的時候可以直接取值。詳細的使用會在代碼中體現(xiàn)。
要讓iOS設備開始傳輸信號還需要做些其他的事情。在viewDidLoad中調(diào)用幾個方法才能讓基站工作起來。首先調(diào)用的是initBeacon方法,之后是要把數(shù)據(jù)現(xiàn)實出來的setLabels方法。
在這里通過uuid字符串設定NSUUID實例。uuid字符串可以通過uuidgen命令在終端里生成。也可以通過代碼生成,但是這里沒有這個必要。然后其他的值major和minor分別設定為1,identifier設定為我們示例的名字。
下面我們就要考慮傳輸?shù)膯栴}了。在用戶點擊了傳輸按鈕之后開始使用設備的藍牙外設發(fā)出信號。方法如下
在這個方法里,首先通過我們設定的beaconRegion屬性獲得相關(guān)的外設數(shù)據(jù)。這個數(shù)據(jù)是NSDictionary類型的。需要說明的是,這個類型可以在Swift中使用,但是和Swift本身的Dictionary是不兼容的。
之后,初始化了CBPeripheralManager屬性。這里簡單處理,設定queue和options為nil。為了設定代理為 self 需要實現(xiàn) CBPeripheralManagerDelegate protocol 。
實現(xiàn)了這個protocol之后,還需要實現(xiàn)這個protocol的一個方法。這個方法在OC里來講是required的,所以必須實現(xiàn)。
外設的狀態(tài)中有兩個需要我們處理的。一個是PoweredOn一個PoweredOff。On了就讓peripheralManager開始對 外發(fā)出信號。Off了就停止發(fā)出信號。所以呢,如果直接在transmitAction中調(diào)用代碼startAdvertising是設備對外發(fā)出信號的 話,會報錯。Console會打印出”CBPeripheralManager is not Powered on“。所以我們在以上的代碼中在Pwoered On的時候再開始調(diào)用代碼發(fā)出信號。發(fā)出信號的時候呢,我們給了advertising方法傳入了從beaconRegion取出來的 NSDictionary數(shù)據(jù)。這也是接收機辨識基站用到的數(shù)據(jù)。
之后我們把數(shù)據(jù)顯示在界面上。
這個方法很簡單,只要在viewDidLoad里調(diào)用setLabels方法就可以。全部設定在基站里的數(shù)據(jù)都會顯示在界面上了。其中包括 uuid、major和minor,最后是我們設定的identifier。用Swift比用OC寫代碼是會少一些量,如果你對Swift足夠熟的話。比 如,取得一個值的字符竄值的時候就不用[NSString stringWithFormat:"..."]這么麻煩的寫法了。
我們的app已經(jīng)可以作為基站使用了。但是沒有接收機的話這個app可是一點都不好玩。在下面就開始處理接收機的部分。
接收iBeacon信息
接收iBeacon信號的底層功能已經(jīng)在Core Location Framework里實現(xiàn)了。在iOS7里,可以自動識別你是否進入了一個區(qū)域以及其他的距離之類的信息。
下面在TrackViewController中處理,首先把CoreLocation庫的頭文件加進來。然后增加下面的兩個屬性:
CLBeaconRegion屬性是用來定義我們要尋找的beacon的。這個app只會接收到有同樣的UUID的的發(fā)射機發(fā)射的信號。
CLLocationManager屬性是用來建立位置服務并搜索beacon的。
在viewDidLoad方法中添加如下代碼:
首先,要初始化CLLocationManager,然后把代理設置為self。當然設定代理之前我么需要實現(xiàn)CLLocationManagerDelegate protocol,具體的就不寫出來了。然后調(diào)用initRegion方法,這個會在后面給出詳情。
首先創(chuàng)建UUID實例。用來初始化這個實例的的uuid字符串必須和基站的uuid字符串是一樣的,要不互相找不見。
然后初始化beaconRegion,proximityUUID就是前面創(chuàng)建的UUID實例,identitifer就是在基站中用到的 identifier字符串。然后開始監(jiān)測前面初始化出來的beaconRegion。調(diào)用代碼 self.trackLocationManager.startMonitoringForRegion(self.beaconRegion)開始檢 測。
接下來,我們需要監(jiān)測這個app進入和離開區(qū)域的事件,代碼如下:
第一個方法是進入的,當你接收到基站信號的時候這個方法就開始執(zhí)行(當然你要離基站足夠近)。然后調(diào)用locationManager的startRagingBeaconsInRegion方法,病傳入我們之前定義好的beaconRegion屬性。
第二個方法很簡單,就是在離開這個區(qū)域的時候就停止執(zhí)行,調(diào)用locationManager的stopRagingBeaconsInRegion。
下面是didRangeBeacons方法:
首先判斷方法傳入的beacons數(shù)組的元素有多少。如果有0個的時候什么都不做。如果有一個或以上的話,這里簡單處理只取最后一個beacon來處理。
之后的代碼都只是從beacon中取出數(shù)據(jù)來現(xiàn)實在界面上,比如UUID,major,minor,accuracy和RSSI。這些值會隨著 接收機和基站的距離不同以及基站的設置不同而一直改變。尤其accuracy和RSSI都是beacon用來現(xiàn)實距離的。其中proximity會顯示四 個值:Unknown、Immediate、Near和Far。Immediate是半米以內(nèi),Near遠一些,F(xiàn)ar更遠。RSSI是信號強度。
運行程序
現(xiàn)在這個app可以運行了。但是需要分別運行在兩臺可以兼容低功耗藍牙的設備上。一臺當基站一臺當接收機。
當你從足夠遠(10~20米)的地方想基站的方向走的時候,一個你”didEnterRange”那個didRangeBeacons方法就會被調(diào)用,這是就可以從方法中傳入的beacon數(shù)組中取出值來現(xiàn)實在界面上。
但是有一點需要注意的是,我們處理的基站只有一個,所以每次都取出beacon數(shù)組的最后一個。如果有多個beacon基站的話就需要循環(huán)beacon數(shù)組的每一個元素,判斷這個元素的proximity是Immdiate還是Near還是Far等。
每次測試的時候都進入或者離開一個區(qū)域太麻煩。可以在viewDidLoad方法中添加一行代碼:
然后添加如下方法:
動調(diào)用了didStartMonitoringForRegion,在這個方法中調(diào)用了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: 1, minor: 1, 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 <= 0 {
- return
- }
- var beacon: AnyObject = beacons[beacons.count - 1]
- 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:
- }
- }
- self.rssiLabel.text = beacon.rssi ? beacon.rssi!.description : ""
- }
- }