原文鏈接: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)。
來看看 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
胞谭、 major
和 minor
。
UUID
是 universally 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è)物品积糯,如下圖所示:
要添加一個(gè)物品,只需要輸入物品的名稱和iBeacon
所對(duì)應(yīng)的值碴犬。你可以在iBeacon
的文檔中找到它的UUID
絮宁,嘗試添加一下,或者隨意輸入一些占位值:
點(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ì)在nameTextField
和uuidTextField
都滿足合法性驗(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
接下來,修改一下 majorValue
和 minorValue
的定義橄唬,如下所示:
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
}
CLBeaconMajorValue
和 CLBeaconMinorValue
都是 UInt16
的別名赋焕,在 CoreLocation
框架中分別用來表示 major
和 minor
值。
盡管他們底層的數(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)。
不幸的是晨逝,你需要添加的鍵并不在預(yù)先定義好的下拉列表里蛾默,所以需要手動(dòng)輸入鍵的名稱。將這個(gè)鍵命名為 NSLocationAlwaysUsageDescription
并且保證它的類型是 String
捉貌。接著支鸡,寫上你需要從用戶那里獲取位置信息的原因,比方說: “ForgetMeNot would like to teach you how to use iBeacons!”趁窃。
編譯運(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 value
和 minor 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
局蚀,Near
,Immediate
三種范圍的確切距離并沒有明確的在文檔中給出恕稠,但是這個(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í)例的 UUID
、 major value
和 minor 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)算符雹舀,尋找 item
和 iBeacon
的對(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
你画、Immediate
和 Unknown
。
在 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ì)把 cell
的 detailTextLabel.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)更新,就像下面這樣:
你可能會(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)備著通知用戶哥蔚,以防他們忘了自己的電腦包或者一不留神丟了貓倒谷,甚至更糟——貓帶著電腦包跑了!:]
他們看起來很天真對(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ì)看到彈出來的通知:
小貼士:文檔中并沒有說,但是蘋果會(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論!