Swift iBeacon App 程式設計

iBeacon 是一種可以發射藍芽訊號的裝置,利用它結合 iOS App 我們可以實現許多有趣的應用。它最主要的應用有以下兩種:

  • 偵測 iPhone 進入某個 iBeacon 的範圍時做某件事。
  • 偵測 iPhone 跟 iBeacon 的距離,依不同距離做不同的事情。

比方彼得潘和溫蒂逛著博物館,走到名畫蒙娜麗莎前,App 偵測到裝在蒙娜麗莎身上的 iBeacon,於是 App 開始唸出蒙娜麗莎的相關介紹,透過 AirPods 傳到彼得潘的耳朵,幫助彼得潘在溫蒂面前變成博學的文青。

想開發偵測 iBeacon 的 iOS App 並不難,但首先我們得先買個 iBeacon 來實驗,比方以下網頁裡可看到玲琅滿目的 iBeacon。

先別急著買,雖然 iBeacon 不貴,但我們手上早有可以當 iBeacon 的玩意呀。開發 iOS App 的我們,大部分都有一台 iPhone & 一台 Mac,只要不是太舊的 iPhone & Mac,其實都有變身成 iBeacon 的藍芽(bluetooth)能力。

將 Mac 或 iPhone 變成 iBeacon

我們可以參考 Apple 官方文件撰寫程式,將 iPhone 變成 iBeacon。

不過這樣的程式早有人幫我們寫好,用不著自己寫。有需要的朋友可參考以下連結下載別人寫好的 Mac App,將 Mac 變成 iBeacon。

啟動 App 後,將看到以下的畫面,我們可調整 iBeacon 的 UUID,Major,Minor & Power。

每個 iBeacon 裝置都有 UUID(128 bit),major(16 bit) & minor(16 bit),因此我們可透過這三個欄位區分不同的 iBeacon。依不同的應用,我們有以下三種設計和辨識 iBeacon 的方法:

  1. 讓 iBeacon 裝置使用同樣的 UUID & major,利用 minor 區分。
  2. 讓 iBeacon 裝置使用同樣的 UUID,利用 major & minor 區分。
  3. 利用 UUID ,major ,minor 區分。

如果不是太複雜的應用,一般用方法一或方法二已足夠。比方博物館裡有十幅畫作,我們可在每幅畫作身上配置 iBeacon,給每個 iBeacon 同樣的 UUID & major,搭配不同的 minor 來區分。

若想修改 UUID,但不知要輸入什麼,可參考以下方法產生 UUID。

Major & Minor 可輸入數字,但記住它只有 16 bit,因此最大只能輸入 65535。

確定 UUID,Major & Minor 後,點選 Start 按鈕,即可讓 Mac 成為 iBeacon,開始發射(broadcast)訊號。

ps: 我們也可以透過 App 讓 iPhone 或 iPad 成為 iBeacon,比方使用 BLE Scanner。

開發偵測 iBeacon 的 iOS App

接下來彼得潘將一步步說明開發 iBeacon iOS App 的步驟,有興趣的朋友也可進一步參考 Apple 官方的說明。

1 加入存取位置的權限文字說明。

依據 Apple 以下表格的說明,為了偵測 iPhone 進入某個 iBeacon 範圍(Raging monitoring) & 量測 iPhone & iBeacon 間的距離 (iBeacon ranging),我們需在 Info 頁面加入以下兩種權限的存取文字說明,Privacy — Location Always and When In Use Usage Description & Privacy — Location When In Use Usage Description。

2 建立 CLLocationManager,將 controller 設為 CLLocationManager 物件的 delegate & 要求存取位置的權限。

import CoreLocation
class ViewController: UIViewController {
var locationManager: CLLocationManager! override func viewDidLoad() { super.viewDidLoad() locationManager = CLLocationManager() locationManager.delegate = self locationManager.requestAlwaysAuthorization()}extension ViewController: CLLocationManagerDelegate {}

3 開始偵側 iBeacon。

func monitorBeacons() {   if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {      let proximityUUID = UUID(uuidString: "B0702880-A295-A8AB-F734-031A98A512DE")      let beaconId = "deeplove"      let region = CLBeaconRegion(proximityUUID: proximityUUID!, identifier: beaconId)      locationManager.startMonitoring(for: region)   }}override func viewDidLoad() {   super.viewDidLoad()   locationManager = CLLocationManager()   locationManager.delegate = self   locationManager.requestAlwaysAuthorization()   monitorBeacons()}

說明:

(1) 利用 CLBeaconRegion 指定想要偵測的 beacon。

let region = CLBeaconRegion(proximityUUID: proximityUUID!, identifier: beaconId)

產生 CLBeaconRegion 的方法有三種,我們可視需求選擇不同的方法。

public init(proximityUUID: UUID, identifier: String)public init(proximityUUID: UUID, major: CLBeaconMajorValue, identifier: String)public init(proximityUUID: UUID, major: CLBeaconMajorValue, minor: CLBeaconMinorValue, identifier: String)

在剛剛的例子裡,我們假設目前創業為艱,只有錢買一個 iBeacon,因此利用 UUID 辨識足已,不需要 major & minor。(ps: 請記得 UUID 要和剛剛在 Mac iBeacon App 上設定的 UUID 一致,如此才能偵測到化身 iBeacon 的 Mac。)

let region = CLBeaconRegion(proximityUUID: proximityUUID!, identifier: beaconId)

(2) 開始偵測是否進入 / 離開 iBeacon 的範圍。

locationManager.startMonitoring(for: region)

4. 定義 CLLocationManagerDelegate 的 function,偵測進入 / 離開 beacon 的範圍。

extension ViewController: CLLocationManagerDelegate {   func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {      print("enter region")   }   func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {      print("exit region")   }}

5 量測 iPhone 跟 beacon 之間的距離

有時我們還想進一步知道 iPhone & iBeacon 之間的距離。如下圖所示,Apple 將距離分成四種等級,由近到遠依序為 Immediate(非常近),Near,Far & Unknown。

Apple 官方 iBeacon proximity(鄰近距離)的說明圖片

比方在看博物館展覽時,我們希望走到畫作前時 App 才呈現相關的資訊,於是我們可從程式判斷距離為 Immediate 時顯示畫作資訊,如此才不會距離畫作還有一大段距離,連畫都看不清楚時就奇怪地顯示畫作資訊。

extension ViewController: CLLocationManagerDelegate {   func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {      print("enter region")      if CLLocationManager.isRangingAvailable() {         locationManager.startRangingBeacons(in: region as! CLBeaconRegion)      }   }
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { print("exit region") locationManager.stopRangingBeacons(in: region as! CLBeaconRegion) }}

說明:

利用 startRangingBeacons & stopRangingBeacons 開始 / 停止偵測 iPhone & beacon 的距離。我們在 locationManager(_:didEnterRegion:) 裡才開始偵測距離,因為偵測距離比較耗電,它將不斷地量測更新距離。

不過有一點值得注意的,實測時發現 locationManager(_:didEnterRegion:) 有可能沒被觸發,進而讓程式不會呼叫 startRangingBeacons,因此永遠不會偵測到 iPhone 已經在 beacon 旁邊。比方若你原本就在 beacon 範圍裡,locationManager(_:didEnterRegion:) 將不會被觸發。因此測試時最好一開始遠離 beacon,在 beacon 範圍之外,然後再慢慢地靠近 beacon,進入 beacon 的範圍,這樣較容易觸發 locationManager(_:didEnterRegion:)。(ps: 若想確保我們一定能偵測到 iPhone 就在 beacon 旁邊,另一種做法是一開始就同時呼叫 startMonitoring & startRangingBeacons)

let region = CLBeaconRegion(proximityUUID: proximityUUID!, identifier: beaconId)locationManager.startMonitoring(for: region)locationManager.startRangingBeacons(in: region)

6 定義 CLLocationManagerDelegate 的 function 量測距離。

從 CLBeacon 物件的 proximity 判斷距離。當我們在 beacon 附近時,此 function 將不斷被呼叫,告訴我們最新的距離資訊。

func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {   if beacons.count > 0 {      let nearestBeacon = beacons[0]      print(nearestBeacon.proximityUUID, nearestBeacon.major, nearestBeacon.minor)      switch nearestBeacon.proximity {      case .immediate:         print("immediate")

case .near:
print("near") case .far: print("far") case .unknown: print("unknown") @unknown default: print("@unknown default") } }}

實驗:

利用 Mac 當 iBeacon,然後啟動剛剛開發的程式偵測 beacon,我們可利用此難得的機會運動一下,走走路讓 iPhone 靠近和遠離 Mac,觀察 locationManager(_:didRangeBeacons:in:) 裡 CLBeacon 距離的變化。

在背景偵測 beacon & 顯示通知

我們也可以在背景偵測 beacon,甚至在 App 沒啟動但進入 beacon 範圍時,由 iOS 自動啟動 App,然後觸發 locationManager:didEnterRegion:。比方以下例子,我們在 locationManager:didEnterRegion: 觸發時顯示通知訊息:

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {   let content = UNMutableNotificationContent()   content.title = "注意"   content.subtitle = "小明就在你身邊"   content.badge = 1   content.sound = UNNotificationSound.default   let request = UNNotificationRequest(identifier: "notification", content: content, trigger: nil)   UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)   if CLLocationManager.isRangingAvailable() {      locationManager.startRangingBeacons(in: region as! CLBeaconRegion)   }}

為了顯示通知,記得要先徵求使用者的同意。

import UIKitimport UserNotifications@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {   var window: UIWindow?func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { granted, error in      if granted {      } else {         print("使用者不同意,不喜歡米花兒,哭哭!")      }   })   return true}

實驗步驟:

  1. 安裝 App,然後啟動 App 同意權限後,先將它殺掉,維持 App 不啟動的狀態。
  2. 把 iPhone 收進胸前左邊口袋,走到遠離 beacon 的天涯海角散心。
  3. 從天涯海角出發,朝著心愛的 beacon 方向前進。

一旦進入 beacon 的訊號範圍,此時將觸發 function locationManager:didEnterRegion:,口袋裡的 iPhone 將傳來通知聲音,顯示著通知訊息,小明就在你身邊

ps:

  1. 若你原本就在 beacon 範圍裡,locationManager(_:didEnterRegion:) 將不會被觸發。因此測試時請記得先遠離 beacon。

2. 根據 Apple 文件的說明,為了在背景偵測到進入 beacon 範圍時啟動 App,我們得將 Capabilities 頁面 Background Modes 的 Location Updates 打開。不過根據實測的結果,不需打開 Location Updates 一樣可觸發 locationManager:didEnterRegion: & application(_:didFinishLaunchingWithOptions:)。

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com