iBeacons iOS 和 Swift 教程

原文鏈接:iBeacons Tutorial with iOS and Swift
原文日期:2015/08/07

譯者:SergioChan
校對(duì):numbbbbb
定稿:shanks

升級(jí)提示:這篇教程已經(jīng)由 Adrian Strahan 更新到支持 iOS8氓轰、Swift 1.2 和 Xcode6.3惶洲。原始文章由 Tutorial Team 成員Chris Wagner所寫飒责。

你有沒有想過有一天你可以在一個(gè)類似購(gòu)物商場(chǎng)或者棒球場(chǎng)這么巨大的建筑中通過手機(jī)找到你所在的位置颁褂?

當(dāng)然可以,GPS 可以告訴你具體位置检盼,但是要想在這些鋼筋混凝土建筑中獲得精確的 GPS 信號(hào)可不是件容易的事肯污。你需要通過建筑內(nèi)部的某種設(shè)施來精確定位你的設(shè)備的物理坐標(biāo)。

使用 Core Location 和 iBeacons 追蹤手機(jī)位置梯皿!
使用 Core Location 和 iBeacons 追蹤手機(jī)位置仇箱!

來看看 iBeacon 吧!在本教程中你將會(huì)創(chuàng)建一個(gè)應(yīng)用程序东羹,它可以關(guān)聯(lián) iBeacon 發(fā)射器并且在你的手機(jī)離開發(fā)射器范圍的時(shí)候收到通知剂桥。在實(shí)際使用中,你可以將 iBeacon 發(fā)射器放置在任何你覺得重要的東西上—手提電腦包属提,錢包权逗,甚至你貓咪的項(xiàng)圈上,通過這個(gè)應(yīng)用程序來追蹤冤议。一旦你的設(shè)備離開了這些發(fā)射器的有效范圍斟薇,應(yīng)用程序就能檢測(cè)到變化并通知你。

如果想跟著教程動(dòng)手做恕酸,你需要一臺(tái) iOS 真機(jī)和一個(gè) iBeacon 設(shè)備堪滨。如果你沒有 iBeacon 但是有另一個(gè) iOS 設(shè)備, 也可以把它當(dāng)做一個(gè)iBeacon來用蕊温,往下讀吧袱箱!

如何開始

其實(shí)有很多的 iBeacon 設(shè)備,谷歌搜索里可以找到很多信息义矛。蘋果介紹 iBeacon 時(shí)宣稱任意 iOS 設(shè)備都可以當(dāng)做一個(gè) iBeacon 使用发笔。下面是可以當(dāng)做 iBeacon 使用的設(shè)備列表:

  • iPhone 4s 或更新的設(shè)備
    
  • 第三代 iPad 或更新的設(shè)備
    
  • iPad Mini 或更新的設(shè)備
    
  • 第五代 iPod touch 或更新的設(shè)備
    

小貼士:如果你沒有 iBeacon 發(fā)射器但是有另一臺(tái)iOS設(shè)備并且支持 iBeacons,可以遵照章節(jié)22—What’s new in Core Location of iOS 7 by Tutorials 去創(chuàng)建一個(gè)模擬 iBeacon 功能的應(yīng)用程序并把它當(dāng)做 iBeacon 發(fā)射器使用凉翻。

iBeacon 其實(shí)就是一個(gè)低功耗藍(lán)牙設(shè)備了讨,通過一種特定的數(shù)據(jù)結(jié)構(gòu)來廣播信息。這些結(jié)構(gòu)的介紹超出了這篇教程的范疇,重要的是知道 iOS 可以監(jiān)聽 iBeacon 發(fā)出的三種值前计,分別是 UUID胞谭、 majorminor

UUIDuniversally unique identifier 的首字母縮寫残炮,這是一個(gè) 128 位的值韭赘,通常以 16 進(jìn)制字符串來顯示,例如:B558CBDA-4472-4211-A350-FF1196FFE8C8势就。在 iBeacon 應(yīng)用中,UUID通常用來標(biāo)識(shí)頂層身份脉漏。

主值(major)和副值(minor)則在UUID的基礎(chǔ)上提供了更細(xì)粒度的劃分苞冯。這兩個(gè)值都是 16 位無符號(hào)整數(shù),他們可以唯一標(biāo)識(shí)每個(gè) iBeacon 侧巨,甚至可以區(qū)分那些UUID相同的設(shè)備舅锄。

比方說,你有很多個(gè)百貨商場(chǎng)司忱,這些商場(chǎng)中的 iBeacon 設(shè)備可以擁有相同的UUID皇忿。雖然所有設(shè)備都有相同的UUID,但是每個(gè)商場(chǎng)都會(huì)有它自己的主值坦仍,商場(chǎng)中的每個(gè)商店都會(huì)有一個(gè)副值鳍烁。這樣你的應(yīng)用程序就可以根據(jù)其中的一個(gè) iBeacon 設(shè)備定位到你在邁阿密,佛羅里達(dá)分店的鞋店繁扎。

ForgetMeNot(勿忘我)新手項(xiàng)目

這個(gè)新手項(xiàng)目的名稱叫做“ForgetMeNot(勿忘我)”幔荒。你可以在這里下載到源代碼,它包括了一個(gè)支持增刪元素的 TableView 界面梳玫。每個(gè)元素表示一個(gè)iBeacon發(fā)射器爹梁,在現(xiàn)實(shí)世界中,你可以將它們理解成那些你想監(jiān)控的物體提澎。

編譯和運(yùn)行應(yīng)用程序姚垃,你會(huì)看到一個(gè)空的列表,什么也沒有盼忌。點(diǎn)擊右上方的 + 按鈕來添加一個(gè)物品积糯,如下圖所示:

初次運(yùn)行
初次運(yùn)行

要添加一個(gè)物品,只需要輸入物品的名稱和iBeacon所對(duì)應(yīng)的值碴犬。你可以在iBeacon的文檔中找到它的UUID絮宁,嘗試添加一下,或者隨意輸入一些占位值:

添加一個(gè)物品
添加一個(gè)物品

點(diǎn)擊 Save 回到物品列表服协,你會(huì)看到一個(gè)位置未知的物品:

已添加物品的列表
已添加物品的列表

你可以根據(jù)自己的需要來添加更多的物品绍昂,也可以滑動(dòng)刪除已經(jīng)添加的物品。NSUserDefaults會(huì)持久化存儲(chǔ)列表中的物品,所以重新打開應(yīng)用程序也不會(huì)丟失這些數(shù)據(jù)窘游。

表面上看什么事都沒發(fā)生唠椭,其實(shí)大多數(shù)有趣的東西都隱藏在界面背后!這個(gè)應(yīng)用程序里最獨(dú)特的地方就是用來表示列表中的物品的Item模型類忍饰。

在 Xcode 中打開Item.swift贪嫂。這個(gè)類映射了界面上所顯示的屬性,并且遵守NSCoding協(xié)議艾蓝,這樣這個(gè)類可以實(shí)現(xiàn)序列化和反序列化力崇,并且可以進(jìn)行持久化存儲(chǔ)。

現(xiàn)在再來看看AddItemViewController.swift赢织。這是一個(gè)用來添加新物品的控制器亮靴。它是個(gè)簡(jiǎn)單的UITableViewController,除此之外還做了一些用戶輸入的合法性驗(yàn)證來保證用戶輸入的名稱和UUID是合法的于置。

頁面右上方的Save按鈕會(huì)在nameTextFielduuidTextField都滿足合法性驗(yàn)證條件的時(shí)候變成可點(diǎn)擊的狀態(tài)茧吊。

現(xiàn)在你已經(jīng)熟悉了這個(gè)項(xiàng)目,可以開始加入iBeacons啦八毯!

Core Location 授權(quán)

你的設(shè)備不會(huì)自動(dòng)監(jiān)聽其他的 iBeacon 設(shè)備搓侄,需要進(jìn)行設(shè)置。CLBeaconRegion 這個(gè)類代表一個(gè) iBeacon话速,CL前綴表示這是 Core Location 框架的一部分讶踪。

因?yàn)?iBeacon 是通過藍(lán)牙來進(jìn)行數(shù)據(jù)傳輸?shù)模虼怂?Core Location 有關(guān)乍一看很奇怪尿孔,但是仔細(xì)想想就會(huì)明白俊柔,iBeacon提供的是高精度的定位信息,而較低精度的定位信息則需要通過 GPS 來獲取活合。如果你把一個(gè) iOS 設(shè)備當(dāng)做 iBeacon 使用雏婶,那就必須使用 Core Bluetooth 框架進(jìn)行平衡,但是當(dāng)你使用真正的 iBeacon 設(shè)備時(shí)白指,只需要掌握 Core Location 就夠了留晚。

好了,接下來開始做第一件事:讓 Item 適用于 CLBeaconRegion告嘲。

打開 Item.swift错维,在文件的頭部加上如下引用:

import CoreLocation

接下來,修改一下 majorValueminorValue 的定義橄唬,如下所示:

let majorValue: CLBeaconMajorValue
let minorValue: CLBeaconMinorValue
 
init(name: String, uuid: NSUUID, 
        majorValue: CLBeaconMajorValue, 
        minorValue: CLBeaconMinorValue) {
  self.name = name
  self.uuid = uuid
  self.majorValue = majorValue
  self.minorValue = minorValue
}

CLBeaconMajorValueCLBeaconMinorValue 都是 UInt16 的別名赋焕,在 CoreLocation 框架中分別用來表示 majorminor 值。

盡管他們底層的數(shù)據(jù)類型一樣仰楚,使用不同的命名能夠增強(qiáng)可讀性隆判,同時(shí)能夠增強(qiáng)安全性 —— 因?yàn)槟悴粫?huì)輕易的弄混他們犬庇。

繼續(xù)!打開 ItemsViewController.swift侨嘀,同樣在文件頭部引用 CoreLocation

import CoreLocation

然后將下面的屬性添加到 ItemsViewController

let locationManager = CLLocationManager()

這個(gè) CLLocationManager 實(shí)例會(huì)成為 CoreLocation 的入口臭挽。

接下來,用下面的方法替換掉你的 viewDidLoad()

override func viewDidLoad() {
  super.viewDidLoad()
 
  locationManager.requestAlwaysAuthorization()
 
  loadItems()
}

調(diào)用 requestAlwaysAuthorization() 將會(huì)提示用戶授權(quán)訪問位置服務(wù) —— 當(dāng)然如果他們已經(jīng)授權(quán)咬腕,系統(tǒng)的提示就不會(huì)出現(xiàn)欢峰。Always(始終)When in Use(使用應(yīng)用程序期間) 是 iOS 8 中位置服務(wù)權(quán)限的新形式。如果用戶給應(yīng)用程序授權(quán)了 Always(始終) 涨共,那無論是前臺(tái)還是后臺(tái)運(yùn)行纽帖,應(yīng)用程序都調(diào)用任何可用的位置服務(wù)。

由于這篇教程將會(huì)使用 iBeacon 的區(qū)域監(jiān)聽功能举反,你需要選擇 Always(始終) 抛计,這樣我們才可以在任何時(shí)候監(jiān)聽到設(shè)備的事件。

iOS 8 要求你在 Info.plist 中設(shè)置一個(gè)字符串作為獲取用戶位置信息的時(shí)候展示給用戶的提示信息照筑。如果你沒有設(shè)置這個(gè),位置服務(wù)不會(huì)生效瘦陈,編譯器甚至都不會(huì)發(fā)出警告凝危。

打開 Info.plist 并點(diǎn)擊 Information Property List 這一行上的 + 來添加一項(xiàng)。

image
image

不幸的是晨逝,你需要添加的鍵并不在預(yù)先定義好的下拉列表里蛾默,所以需要手動(dòng)輸入鍵的名稱。將這個(gè)鍵命名為 NSLocationAlwaysUsageDescription 并且保證它的類型是 String捉貌。接著支鸡,寫上你需要從用戶那里獲取位置信息的原因,比方說: “ForgetMeNot would like to teach you how to use iBeacons!”趁窃。

image
image

編譯運(yùn)行你的應(yīng)用程序牧挣,你將會(huì)看到一個(gè)獲取位置信息的提示信息:

允許獲取位置信息
允許獲取位置信息

選擇同意,這樣程序就可以追蹤你的 iBeacon 的位置啦醒陆。

監(jiān)聽你的 iBeacon

現(xiàn)在你的應(yīng)用程序已經(jīng)擁有了必要的位置信息獲得權(quán)限瀑构,是時(shí)候去找到這些設(shè)備了!在 ItemsViewController.swift 的底部添加如下的類擴(kuò)展:

// MARK: - CLLocationManagerDelegate
 
extension ItemsViewController: CLLocationManagerDelegate {
}

這將聲明 ItemsViewController 遵守 CLLocationManagerDelegate 協(xié)議刨摩。你可以在這個(gè)擴(kuò)展中添加委托方法的實(shí)現(xiàn)寺晌,這樣就可以很好地把他們組織到一起。

接下來澡刹,在 viewDidLoad() 的結(jié)尾加上下面這行:

locationManager.delegate = self

這會(huì)將 CLLocationManager 的委托指向 self呻征,從而接收委托方法的回調(diào)。

現(xiàn)在你擁有了一個(gè) CLLocationManager 實(shí)例罢浇,可以使用 CLBeaconRegion 來讓應(yīng)用程序監(jiān)聽指定區(qū)域啦陆赋!如果注冊(cè)了一個(gè)需要監(jiān)聽的區(qū)域沐祷,無論程序是否啟動(dòng)這些區(qū)域都一直存在。這樣即使你的程序沒有運(yùn)行奏甫,也可以監(jiān)聽區(qū)域邊界的觸發(fā)事件戈轿。

列表中的 iBeacon 設(shè)備對(duì)應(yīng)的是 items 數(shù)組中的 Item 模型。CLLocationManager 需要傳入 CLBeaconRegion 實(shí)例阵子。

ItemsViewController.swift 中的 ItemsViewController 類中創(chuàng)建下述方法:

func beaconRegionWithItem(item:Item) -> CLBeaconRegion {
  let beaconRegion = CLBeaconRegion(proximityUUID: item.uuid,
                                            major: item.majorValue,
                                            minor: item.minorValue,
                                       identifier: item.name)
  return beaconRegion
}

這會(huì)用輸入的 Item 對(duì)象創(chuàng)建一個(gè)新的 CLBeaconRegion 實(shí)例思杯。

可以看到這些類在結(jié)構(gòu)上很相似,因此可以直接創(chuàng)建 CLBeaconRegion 實(shí)例 —— 它有對(duì)應(yīng)的 UUID 挠进、 major valueminor value 屬性色乾。

現(xiàn)在你需要一個(gè)方法來監(jiān)聽設(shè)備。接下來在 ItemsViewController 中添加下述方法:

func startMonitoringItem(item: Item) {
  let beaconRegion = beaconRegionWithItem(item)
  locationManager.startMonitoringForRegion(beaconRegion)
  locationManager.startRangingBeaconsInRegion(beaconRegion)
}

這個(gè)方法需要傳入一個(gè) Item 的實(shí)例领突,通過剛才定義的方法生成一個(gè) CLBeaconRegion 實(shí)例暖璧,然后讓 locationManager 開始監(jiān)聽給定的區(qū)域,并且在這個(gè)區(qū)域內(nèi)搜索 iBeacon君旦。

搜索是在指定區(qū)域發(fā)現(xiàn) iBeacon 設(shè)備并確定距離的過程澎办。當(dāng)一個(gè) iOS 設(shè)備收到來自 iBeacon 設(shè)備的傳輸信息時(shí),能夠較為精確的計(jì)算出從 iBeacon 到 iOS 設(shè)備的距離金砍。這個(gè)距離(在發(fā)送消息的 iBeacon 和接收消息的設(shè)備之間的距離)被分類成3種不同的范圍:

  • Immediate 只有幾厘米
  • Near 幾米以內(nèi)
  • Far 10米以上

小貼士:Far局蚀,NearImmediate 三種范圍的確切距離并沒有明確的在文檔中給出恕稠,但是這個(gè) StackOverflow 的提問對(duì)這些距離的確切值提供了一個(gè)大致的范圍琅绅。

默認(rèn)情況下,無論你的應(yīng)用是否在運(yùn)行鹅巍,監(jiān)控程序都會(huì)在區(qū)域有物體進(jìn)入和離開的時(shí)候通知你千扶。搜索的不同之處是可以在應(yīng)用運(yùn)行的狀態(tài)下監(jiān)聽物體在區(qū)域中的距離。

你還需要在刪除設(shè)備時(shí)停止對(duì)它的區(qū)域的監(jiān)聽骆捧,在 ItemsViewController 中添加下面的方法:

func stopMonitoringItem(item: Item) {
  let beaconRegion = beaconRegionWithItem(item)
  locationManager.stopMonitoringForRegion(beaconRegion)
  locationManager.stopRangingBeaconsInRegion(beaconRegion)
}

上述方法只是反轉(zhuǎn)了 startMonitoringItem(_:) 產(chǎn)生的結(jié)果并且讓 CLLocationManager 停止監(jiān)聽和搜索操作澎羞。

現(xiàn)在你已經(jīng)完成了開始和結(jié)束的方法,是時(shí)候讓他們發(fā)揮作用了凑懂!開始監(jiān)聽最合適的時(shí)機(jī)自然是用戶向列表中添加新設(shè)備時(shí)煤痕。

看一看 ItemsViewController.swift 中的 saveItem(_:) 方法,這個(gè) unwind segue 會(huì)在用戶點(diǎn)擊 AddItemViewController 中的保存按鈕的時(shí)候被調(diào)用并且創(chuàng)建一個(gè)新的 Item 對(duì)象接谨。找到方法中對(duì) persistItems() 的調(diào)用摆碉,在這個(gè)調(diào)用前加上這行代碼:

startMonitoringItem(newItem)

這將會(huì)在用戶保存一個(gè)新的 item 時(shí)開始監(jiān)聽。同理脓豪,當(dāng)應(yīng)用程序啟動(dòng)的時(shí)候巷帝,程序會(huì)從 NSUserDefaults 加載持久化存儲(chǔ)的數(shù)據(jù),這也意味著你需要在啟動(dòng)的時(shí)候調(diào)用開始監(jiān)聽的方法扫夜。

ItemsViewController.swift 中找到 loadItems() 這個(gè)方法然后在內(nèi)部的 for 循環(huán)中添加這行代碼:

startMonitoringItem(item)

這能保證每一個(gè) item 都被監(jiān)聽到楞泼。

現(xiàn)在我們要處理刪除 item 的操作驰徊。找到 tableView(_:commitEditingStyle:forRowAtIndexPath:) 然后在 itemToRemove 的聲明之后添加這行代碼:

stopMonitoringItem(itemToRemove)

這個(gè) tableview 的委托方法會(huì)在用戶刪除任意一行時(shí)被調(diào)用。現(xiàn)有的代碼處理了模型和界面的刪除操作堕阔,你剛剛添加的那行代碼也會(huì)使你的程序停止監(jiān)聽這個(gè) item棍厂。

到現(xiàn)在為止,你已經(jīng)實(shí)現(xiàn)了很多東西超陆!你的程序現(xiàn)在已經(jīng)能夠?qū)χ付ǖ?iBeacon 設(shè)備啟動(dòng)和停止監(jiān)聽了牺弹。

現(xiàn)在,你可以編譯運(yùn)行一下程序时呀。但是你會(huì)發(fā)現(xiàn)张漂,即使 iBeacon 設(shè)備在應(yīng)用程序的監(jiān)聽范圍內(nèi),程序也不會(huì)響應(yīng)任何事件谨娜,下面我們來解決這個(gè)問題航攒!

響應(yīng) iBeacon 的發(fā)現(xiàn)事件

你的 location manager 已經(jīng)在監(jiān)聽 iBeacon 設(shè)備,下面需要實(shí)現(xiàn)一些 CLLocationManagerDelegate 方法來響應(yīng)監(jiān)聽事件趴梢。

首要任務(wù)是添加一些錯(cuò)誤處理漠畜,因?yàn)槟阏谔幚碓O(shè)備的硬件特性,如果沒有詳細(xì)的錯(cuò)誤提示坞靶,監(jiān)聽或者搜索失敗的時(shí)候你根本無從知曉盆驹。

在你剛才定義的 CLLocationManagerDelegate 委托的類擴(kuò)展中添加下述兩個(gè)方法:

func locationManager(manager: CLLocationManager!, 
monitoringDidFailForRegion region: CLRegion!, 
withError error: NSError!) {
  println("Failed monitoring region: \(error.description)")
}
 
func locationManager(manager: CLLocationManager!, 
didFailWithError error: NSError!) {
  println("Location manager failed: \(error.description)")
}

這兩個(gè)方法會(huì)打印出監(jiān)聽 iBeacons 時(shí)可能出現(xiàn)的錯(cuò)誤。

當(dāng)然滩愁,如果一切順利,你永遠(yuǎn)也不會(huì)看到這兩個(gè)方法的輸出辫封。然而硝枉,一旦發(fā)生了錯(cuò)誤,打印出來的錯(cuò)誤信息會(huì)非常有用倦微。

下一步我們需要實(shí)時(shí)顯示 iBeacon 設(shè)備的距離妻味。在 CLLocationManagerDelegate 類擴(kuò)展中添加下述這個(gè)方法,它暫時(shí)沒有返回值:

func locationManager(manager: CLLocationManager!, didRangeBeacons beacons: [AnyObject]!, inRegion region: CLBeaconRegion!) {
  if let beacons = beacons as? [CLBeacon] {
    for beacon in beacons {
      for item in items {
        // TODO: Determine if item is equal to ranged beacon
      }
    }
  }
}

這個(gè)委托方法會(huì)在 iBeacon 設(shè)備進(jìn)入監(jiān)聽范圍欣福、離開監(jiān)聽范圍或者監(jiān)聽范圍改變時(shí)被調(diào)用责球。

你的程序會(huì)通過這個(gè)委托方法提供的 iBeacon 數(shù)組來更新列表中的數(shù)據(jù)并且顯示它們的距離。你需要先遍歷 beacons 數(shù)組再遍歷你的 items 數(shù)組拓劝,從而找到對(duì)應(yīng)的模型雏逾。之后我們會(huì)回來處理 TODO

進(jìn)入 Item.swift郑临,在 Item 類中添加下述屬性:

dynamic var lastSeenBeacon: CLBeacon?

這個(gè)屬性保存了指定 item 最后對(duì)應(yīng)的 CLBeacon 實(shí)例栖博,這會(huì)被用來顯示距離信息。這個(gè)屬性有一個(gè) dynamic 修飾符厢洞,這樣你才能在接下來的教程中為其添加一個(gè) KVO仇让。

現(xiàn)在在文件底部添加這個(gè)函數(shù),重載等式操作符,記得要添加在類的定義之外:

func ==(item: Item, beacon: CLBeacon) -> Bool {
  return ((beacon.proximityUUID.UUIDString == item.uuid.UUIDString)
    && (Int(beacon.major) == Int(item.majorValue))
    && (Int(beacon.minor) == Int(item.minorValue)))
}

這個(gè)等式方法會(huì)比較一個(gè) CLBeacon 實(shí)例和一個(gè) Item 實(shí)例是否相等 —— 這意味著三個(gè)主要的屬性都相等牺荠。在這種情況下期虾,如果一個(gè) CLBeacon 實(shí)例和一個(gè) Item 實(shí)例的 UUIDmajor valueminor value 都相等踊淳,這兩者就是相等的假瞬。

現(xiàn)在你需要通過調(diào)用上面的方法來完成搜索的委托方法。打開 ItemsViewController.swift嚣崭,找到 locationManager(_:didRangeBeacons:inRegion:)笨触。用下述代碼替換掉 for 循環(huán)中的 TODO 注釋:

if item == beacon {
  item.lastSeenBeacon = beacon
}

匹配到一個(gè) item 對(duì)應(yīng)的 iBeacon 設(shè)備時(shí)設(shè)置它的 lastSeenBeacon。因?yàn)槟阒剌d了等式運(yùn)算符雹舀,尋找 itemiBeacon 的對(duì)應(yīng)關(guān)系變得非常簡(jiǎn)單芦劣!

現(xiàn)在,是時(shí)候使用這個(gè)屬性來顯示搜索到的 iBeacon 的距離了说榆。

進(jìn)入 ItemCell.swift虚吟,在 item 的屬性觀察器 didSet 方法開頭添加下述代碼:

item?.addObserver(self, forKeyPath: "lastSeenBeacon", options: .New, context: nil)

當(dāng)你為每一個(gè) cell 設(shè)置 item 時(shí),你同時(shí)也添加了一個(gè)對(duì) lastSeenBeacon 屬性的觀察器签财。對(duì)應(yīng)的串慰,你需要移除 cell 中已有 item 的觀察器,這也是 KVO 模式的要求唱蒸。在 didSet 后添加一個(gè) willSet 屬性觀察器邦鲫。確保它仍然在 item 屬性中。

willSet {
  if let thisItem = item {
    thisItem.removeObserver(self, forKeyPath: "lastSeenBeacon")
  }
}

這能保證只有一個(gè)屬性正在被觀察神汹。

你還需要在 cell 被釋放時(shí)移除觀察器庆捺。在 ItemCell.swift 中的 ItemCell 類中添加下述釋放方法:

deinit {
  item?.removeObserver(self, forKeyPath: "lastSeenBeacon")
}

現(xiàn)在你已經(jīng)實(shí)現(xiàn)了屬性的觀察,可以添加一些邏輯來處理 iBeacon 的距離變化事件屁魏。

每個(gè) CLBeacon 實(shí)例都有一個(gè)名為 proximity 的屬性滔以,這是一個(gè)枚舉類型,值為 Far 氓拼、Near 你画、ImmediateUnknown

ItemCell.swift 中桃漾,引用 CoreLocation

import CoreLocation

接下來坏匪,在 ItemCell 類中添加下述方法:

func nameForProximity(proximity: CLProximity) -> String {
  switch proximity {
  case .Unknown:
    return "Unknown"
  case .Immediate:
    return "Immediate"
  case .Near:
    return "Near"
  case .Far:
    return "Far"
  }
}

這會(huì)根據(jù) proximity 返回一個(gè)我們能看懂的字符串,下面會(huì)用到它撬统。

現(xiàn)在添加下述方法:

override func observeValueForKeyPath(keyPath: String, 
ofObject object: AnyObject, 
change: [NSObject : AnyObject], 
context: UnsafeMutablePointer<Void>) {
  if let anItem = object as? Item 
  where anItem == item && keyPath == "lastSeenBeacon" {
    let proximity = nameForProximity(anItem.lastSeenBeacon!.proximity)
    let accuracy = String(format: "%.2f", 
    anItem.lastSeenBeacon!.accuracy)
    detailTextLabel!.text = "Location: \(proximity) (approx. \(accuracy)m)"
  }
}

每次 lastSeenBeacon 的值變化時(shí)都會(huì)調(diào)用這個(gè)方法剥槐,它會(huì)把 celldetailTextLabel.text 屬性設(shè)置為 CLBeacon 的感知距離和大致的精度。

后面的那個(gè)值也許會(huì)因?yàn)殡娮痈蓴_產(chǎn)生波動(dòng)宪摧,即使你的設(shè)備和 iBeacon 設(shè)備都沒有移動(dòng)粒竖,也可能會(huì)變化颅崩,所以用它來計(jì)算 iBeacon 的精確位置并不可靠。

現(xiàn)在確保你的 iBeacon 設(shè)備已經(jīng)添加到程序中蕊苗,移動(dòng)設(shè)備來改變他們之間的距離沿后。你會(huì)看到標(biāo)簽的內(nèi)容會(huì)隨著你的移動(dòng)更新,就像下面這樣:

image
image

你可能會(huì)發(fā)現(xiàn)這個(gè)感知距離和精度完全受 iBeacon 所在的物理位置影響朽砰。如果 iBeacon 被放在包或是一個(gè)盒子里尖滚, 它的信號(hào)可能就被屏蔽了 —— 因?yàn)樗且粋€(gè)非常低功率的設(shè)備,信號(hào)很容易被削弱瞧柔。

在設(shè)計(jì)程序時(shí)牢記這一點(diǎn) —— 當(dāng)然漆弄,放置 iBeacon 設(shè)備時(shí)也需要注意。

通知

到這里看起來我們已經(jīng)做得足夠好了:你有了一個(gè) iBeacon 設(shè)備列表并且可以實(shí)時(shí)監(jiān)聽它們和你的距離造锅。但這并不是終極目標(biāo)撼唾。應(yīng)用程序沒有運(yùn)行時(shí),你仍然需要時(shí)刻準(zhǔn)備著通知用戶哥蔚,以防他們忘了自己的電腦包或者一不留神丟了貓倒谷,甚至更糟——貓帶著電腦包跑了!:]

image
image

他們看起來很天真對(duì)不對(duì)糙箍?

你可能已經(jīng)注意到渤愁,我們只用很少的代碼就實(shí)現(xiàn)了 iBeacon 的基本功能,添加一個(gè)通知功能同樣很簡(jiǎn)單深夯!

進(jìn)入 AppDelegate.swift抖格,添加下述引用:

import CoreLocation

接下來,使 AppDelegate 類遵守 CLLocationManagerDelegate 協(xié)議咕晋,在 AppDelegate.swift 底部添加下述代碼(在右大括號(hào)下方):

// MARK: - CLLocationManagerDelegate
extension AppDelegate: CLLocationManagerDelegate {
}

像之前一樣他挎,你需要初始化 location manager 并且設(shè)置它的委托。

Appdelegate 類添加一個(gè)新的 locationManager 屬性捡需,并用一個(gè) CLLocationManager 實(shí)例來初始化它:

let locationManager = CLLocationManager()

然后在 application(_:didFinishLaunchingWithOptions:) 的開始處添加下述聲明:

locationManager.delegate = self

回想一下,之前我們用 startMonitoringForRegion(_:) 來添加并監(jiān)聽區(qū)域筹淫,它們?cè)趹?yīng)用程序中可以被所有 location manager 共享站辉。當(dāng)一個(gè)區(qū)域的邊界被觸發(fā)時(shí),Core Location 會(huì)喚醒你的程序损姜,我們只需完成最后一步:響應(yīng)這個(gè)事件饰剥。

還記得剛才你在 AppDelegate.swift 中添加的類擴(kuò)展嗎?在里面添加下述方法:

func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
  if let beaconRegion = region as? CLBeaconRegion {
    var notification = UILocalNotification()
    notification.alertBody = "Are you forgetting something?"
    notification.soundName = "Default"
    UIApplication.sharedApplication().presentLocalNotificationNow(notification)
  }
}

location manager 會(huì)在你離開一個(gè)區(qū)域時(shí)調(diào)用上面的方法摧阅,這也是你的應(yīng)用程序有趣的一點(diǎn)汰蓉。你不需要在靠近筆記本電腦包時(shí)收到通知 —— 只有你離開它太遠(yuǎn)的時(shí)候才會(huì)。

這里你需要檢查 region 是否是一個(gè) CLBeaconRegion 類的實(shí)例棒卷,因?yàn)樗灿锌赡苁?CLCircularRegion 的實(shí)例 —— 那表示你在監(jiān)聽地理位置區(qū)域顾孽。檢查完畢后發(fā)送一個(gè)本地的通知祝钢,內(nèi)容為“你是不是遺忘了什么?”若厚。

在 iOS 8 和之后的版本里拦英,無論是本地還是遠(yuǎn)程推送,應(yīng)用程序都需要注冊(cè)想要傳遞的通知類型测秸。這樣系統(tǒng)就可以讓用戶來選擇接受的通知類型疤估。如果相應(yīng)的通知的類型在你的應(yīng)用程序中沒有被注冊(cè),即使你在通知里聲明了通知類型霎冯,系統(tǒng)也不會(huì)顯示數(shù)量角標(biāo)和提示信息铃拇,也不會(huì)有新通知的提示音。

application(_:didFinishLaunchingWithOptions:) 開頭添加下述代碼:

let notificationType:UIUserNotificationType = 
UIUserNotificationType.Sound | UIUserNotificationType.Alert
let notificationSettings = UIUserNotificationSettings(
forTypes: notificationType, 
categories: nil)
UIApplication.sharedApplication().
registerUserNotificationSettings(notificationSettings)

這里只是簡(jiǎn)單地進(jìn)行聲明沈撞,當(dāng)應(yīng)用收到新通知時(shí)慷荔,系統(tǒng)會(huì)顯示提示消息并播放提示音。

編譯運(yùn)行你的程序关串,確保程序里有至少一個(gè)已注冊(cè)的 iBeacon 設(shè)備拧廊,然后按下 Home 鍵讓程序在后臺(tái)運(yùn)行。這是一個(gè)現(xiàn)實(shí)生活中的場(chǎng)景晋修,特別是當(dāng)你正在使用其他程序(比如 Ray Wenderlich 的教學(xué)應(yīng)用 :])或者做其他事情時(shí)吧碾,你需要應(yīng)用程序主動(dòng)提醒你。現(xiàn)在拿走你的 iBeacon墓卦,一旦離得足夠遠(yuǎn)倦春,你就會(huì)看到彈出來的通知:

image
image

小貼士:文檔中并沒有說,但是蘋果會(huì)延遲離開區(qū)域的通知落剪。這也許是故意的睁本,這樣你的應(yīng)用程序就不會(huì)收到一大堆推送,特別是當(dāng)你正在區(qū)域的邊緣來回移動(dòng)或者 iBeacon 設(shè)備的信號(hào)時(shí)斷時(shí)續(xù)的時(shí)候忠怖。根據(jù)我的經(jīng)驗(yàn)來看呢堰,離開區(qū)域的通知通常會(huì)在 iBeacon 設(shè)備離開區(qū)域差不多一分鐘之后出現(xiàn)。

我還需要做什么凡泣?

無法綁定 iBeacon 設(shè)備枉疼?你可以在這里下載最終的工程,這里面有你在這個(gè)教程里需要完成的所有東西鞋拟。

你現(xiàn)在有了一個(gè)非常有用的應(yīng)用程序骂维,可以用它來幫你監(jiān)控那些容易丟的物品。只需要一點(diǎn)點(diǎn)的想象力和高超的編程技巧贺纲,你就可以為這個(gè)程序添加許多超級(jí)有用的功能:

  • 告知用戶哪一個(gè)設(shè)備離開了區(qū)域
  • 重復(fù)發(fā)送通知航闺,確保用戶看到了通知
  • 當(dāng)iBeacon設(shè)備回到區(qū)域中的時(shí)候通知用戶

這篇教程只展示了 iBeacon 的冰山一角。

iBeacon 不僅僅局限于自定義的應(yīng)用程序,你可以把他們當(dāng)成 Passbook 的通行證來使用潦刃。比方說侮措,如果你運(yùn)營(yíng)著一個(gè)電影院,你可以將你的電影票以 Passbook 的通行證的形式賣出去福铅。當(dāng)你的老顧客們走過一個(gè)帶有 iBeacon 設(shè)備的收票員的時(shí)候萝毛,他們的應(yīng)用程序就會(huì)自動(dòng)的把票顯示在他們的手機(jī)上了!

如果你關(guān)于這篇教程還有任何問題或者評(píng)論滑黔,或者你對(duì)于 iBeacon 的使用有什么屌炸天的想法笆包,敬請(qǐng)?jiān)u論!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末略荡,一起剝皮案震驚了整個(gè)濱河市庵佣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汛兜,老刑警劉巖巴粪,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粥谬,居然都是意外死亡肛根,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門漏策,熙熙樓的掌柜王于貴愁眉苦臉地迎上來派哲,“玉大人,你說我怎么就攤上這事掺喻“沤欤” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵感耙,是天一觀的道長(zhǎng)褂乍。 經(jīng)常有香客問我,道長(zhǎng)即硼,這世上最難降的妖魔是什么逃片? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮只酥,結(jié)果婚禮上褥实,老公的妹妹穿的比我還像新娘。我一直安慰自己层皱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布赠潦。 她就那樣靜靜地躺著叫胖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪她奥。 梳的紋絲不亂的頭發(fā)上瓮增,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天怎棱,我揣著相機(jī)與錄音,去河邊找鬼绷跑。 笑死拳恋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砸捏。 我是一名探鬼主播谬运,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼垦藏!你這毒婦竟也來了梆暖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤掂骏,失蹤者是張志新(化名)和其女友劉穎轰驳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弟灼,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡级解,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了田绑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勤哗。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辛馆,靈堂內(nèi)的尸體忽然破棺而出俺陋,到底是詐尸還是另有隱情,我是刑警寧澤昙篙,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布腊状,位于F島的核電站,受9級(jí)特大地震影響苔可,放射性物質(zhì)發(fā)生泄漏缴挖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一焚辅、第九天 我趴在偏房一處隱蔽的房頂上張望映屋。 院中可真熱鬧,春花似錦同蜻、人聲如沸棚点。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘫析。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贬循,已是汗流浹背咸包。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杖虾,地道東北人烂瘫。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像奇适,于是被迫代替她去往敵國(guó)和親坟比。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • @end 與之前一樣滤愕,你需要初始化位置管理器并設(shè)置它們的 delegate 温算。 在 application:did...
    LiWeiJ閱讀 2,123評(píng)論 0 0
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件间影、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評(píng)論 4 62
  • 早上看到科比賽季結(jié)束退役的新聞魂贬,然后讀了科比的完整推文巩割。通篇就像是一首詩,勾起了我內(nèi)心的小波瀾付燥。接觸籃球是因?yàn)橛螒?..
    Dr_Dan閱讀 217評(píng)論 0 0