分享

实例教程:快速上手iOS iBeacon开发(Swift版)

 wintelsui 2015-09-02

本文由CocoaChina译者培子翻译自Raywenderlich
原文:iBeacons Tutorial with iOS and Swift


iBeacons-250x250.png备注:该教程针是Adrian Strahan针对iOS 8,swift1.2,以及Xcode6.3而更新的。原始文章由Tutorial Team成员Chris Wagner供稿。

你曾经想过用手机在一个大型建筑物中为自己定位吗,比如购物中心,或者棒球场

当然,GPS可以让你得知自己身处哪一座建筑物里。但是如果想要在这些钢筋混凝土堆砌而成的建筑中获得精确的GPS信号,只能祝你好运了。你所需要的是内置在建筑物中一些设备,(通过它们)让手机获取确定你的位置。

iBeacon,我们来了!在这篇iBeacons教程中,将会开发一个App,功能包括,纪录已知的iBeacon发射器;还有当你的手机设备移出它的信号辐射范围时,App会提醒你。这个App的一大用处是:将iBeacon发射器放置在你的电脑包、钱包里,甚至绑在猫咪的项圈上,以及其他贵重物品上等。一旦设备移出iBeacon辐射范围,App就会检测到并且通知你。

如果想继续深入,你需要一台iOS设备和一个iBeacon设备。如果没有iBeacon设备,但还有另外一个iOS设备,你也可以把它当作iBeacon设备来用。

开始吧


目前有很多可用的iBeacon设备,在Google一搜一大堆。但是苹果公司引进iBeacon时,他们还声称任何兼容iOS的设备都能充当iBeacon。目前包括以下设备:

  • iPhone4s 或者 之后的iPhone设备

  • 第三代iPad或者 之后iPad设备

  • iPad mini 或者 之后iPad mini设备

  • 第五代iPod touch 或者之后iPod touch设备

备注:如果没有单独的iBeacon发射器,但拥有一台支持iBeacons的iOS设备,你可以对照《what's new in core location of iOS 7 by Tutorials》书中第22章的描述,开发一个充当iBeacon角色的App。

iBeacon就是一种低功耗蓝牙设备,它能以特定的数据结构广播数据信息。这些属性虽然超出了本篇教程的讨论范围,但是对理解iOS能够监测iBeacon设备很重要,这些设备广播三个数据:UUID,major和minor。UUID是universally unique identifier的首字母缩写,它是一个128位的值,通常以十六进制的方式显示:B558CBDA-4472-4211-A350-FF1196FFE8C8

在iBeacon的环境下,UUID用来表示设备的最高级别标识。

Major和minor值则是在UUID下提供更细微的辨别标识。它们的值用16位无符号整型数简单表示,用来识别单个iBeacon设备,即使这些设备有着相同的UUID。

例如,你有多家百货公司,打算让所有的iBeacon设备发射相同的UUID信息,但是每家百货公司拥有各自的major值,并且在每家百货公司的各部门又拥有各自的minor值。这样App就可以对放置在迈阿密的弗罗里达分店的鞋业部门的iBeacon设备做相应的信息反馈。

ForgetMeNot 开始项目


这儿下载工程启动文件,它的界面很简单,就是在表格视图中添加和删除对象。表格里的每一个对象代表一个iBeacon设备,在现实生活中,它可以代表你不想遗失的东西。

启动并构建App,你会看到一个空的列表,没有任何对象。点击+按钮,为它添加新项,如下:

firstlaunch-281x500.png

首屏

添加新项,你需要为新对象命名,还有与之对应的值。可以通过查看iBeacon的文档来获得它的UUID。立即添加进去吧,或者用一些占位符值,如下:

additem-281x500.png
添加项目

点击save按钮,返回到列表界面,就会看到location显示为Unknown的对象,如下:

itemadded-281x500.png
添加的项目列表

想添加多少,就添加多少,或者删掉已有的。NSUserDefaults会保存列表里的选项,以便再次打开App时可用。

界面上,看上去也就那么回事;最有趣的部分藏在了表象之下。该App的独特之处就在于表格里显示的Item类。

在Xcode中打开Item.swift。该类对应着界面从用户那里请求的信息,它遵循NSCoding协议,因此可以被串行和并行的存储到硬盘上。

现在看一下AddItemViewController.swift。这是用来添加新Item对象的视图控制器。除了对用户输入做了些验证,确保用户输入有效的名称和UUID之外,它就是一个简单的UITableViewController

一旦nameTextFielduuidTextField内容有效,右上角的Save按钮就会变为可点击状态了。

既然已经熟悉了项目启动文件,你就能够在你的工程中实现iBeacon了。

Core Location 许可


你的设备当然不会自动监测iBeacon的,所以首先你得告知它。CLBeaconRegion类代表一个iBeacon;CL前缀的类表示它属于Core Location框架。

iBeacon与Core Location关联在一起看上去有点奇怪,因为它就是一个蓝牙设备而已,但是也可以这么认为,那就是iBeacon提供小范围定位功能,而GPS提供的是大范围定位功能。当想让iOS设备充当iBeacon时,你还需要引入Core Bluetooth框架,但只想检测iBeacon设备,你只需要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型,用来表示major和minor值。

虽然它们的数据类型一样,但是为了提高Item的可读性和增加数据的安全性,你最好不要把major和minor值搞混。

打开ItemsViewController.swift,在顶部引入Core Location:

import CoreLocation

为其添加如下属性:

let locationManager = CLLocationManager()

当引入Core Location功能时,需要用到这个CLLocationManager对象。

然后,更新viewDidLoad(),如下:

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

如果设备没有给App授权,它就会调用requestAlwaysAuthorization()方法来提示用户是否允许使用定位服务。在iOS8中,AlwaysWhen in Use是最新有关定位授权的状态。在App使用Always权限授权时,只要app在前台或者后台处于运行状态,它就可以启动所有可用的定位服务。

因为该教程对iBeacon一直进行区域监测,所以当app处于前台或者后台运行时,需要Always定位许可来触发区域事件。

iOS8要求你在Info.plist?中设置一串字符,该字符串会在app请求定位服务时显示出来。如果不设置它,定位服务就会无效,甚至都得不到任何警告!

打开Info.plist,选中Information Property List 后,点击+,添加新的一行。

plistwithoutentry.png

遗憾的是,需要添加的key不是在下拉列表中预定义好的,需要自己输入进去。把key设置为NSLocationAlwaysUsageDescription,Type设为String类型。然后输入提示文字,告诉用户用户为何要开启定位服务,例如:"ForgetMeNot would like to teach you how to use iBeacons!"

plistwithentry.png

启动并构建app,一旦运行,你会看到一段消息,询问你是否允许app使用定位服务:

allowlocation-281x500.jpg

允许访问位置

选择Allow,app就能够追踪iBeacon设备了

监听iBeacon


现在app有了定位服务的授权,是时候搜索beacon设备了!在ItemsViewController.swift的底部添加类extension,如下:

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

这段代码表示ItemsViewController遵循CLLocationManagerDelegate协议。接着在extension里添加委托方法,让它们结合在一起。

viewDidLoad方法最后一行添加如下代码:

locationManager.delegate = self

设置CLLocationManager的委托对象为self,以便接收委托方法的回调。

有了CLLocationManager对象,你可以指导app使用CLBeaconRegion对一些指定的区域开启监测。当注册了一个监控区域,之后只要启动app,这些区域就会存在下去。当你对一个交叉区域的边界作出反应,而app没有运行时,这点很重要。

列表中的iBeacon对象由Item类的items数组属性表示。然而CLLocationManager希望你提供一个CLBeaconRegion对象来开启监控。

ItemsViewController.swift中创建如下辅助方法:

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

该方法通过提供的Item,返回一个CLBeaconRegion对象。

可以看出CLLBeaconRegionItem之间有相似的数据结构,所以生成CLBeaconRegion对象很简单,因为它有直接对应的属性UUID,major值和minor值

现在创建一个方法来监控已有的Item对象,给ItemsViewController添加如下代码:

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

该方法使用一个Item参数,调用之前定义的方法生成CLBeaconRegion。然后让location manager开始监控已有的区域,并在该区域内检索iBeacon设备。

在给定的区域内,检索就是发现iBeacon设备的过程,并确定iBeacon设备与iOS设备之间的距离。一台接收到iBeacon发射信息的iOS设备能估算出它与iBeacon之间的距离。这个距离呗划分为三个区域范围:

  • Immediate 几厘米之内

  • Near 几米之内

  • Far 10米开外

备注:Far,NearImmediate对应的实际距离不是固定的,在Stack Overflow Question有提到它,并给它一个组略的距离范围。

默认来讲,无论app是否运行,只要你进入或者走出监控区域,app都会通知你。另一方面,检索iBeacon设备这一过程只会在app处于运行状态时才会监测出区域距离。

还需要在某个Item区域被删除时,终止对它的监控。在ItemViewController添加如下代码:

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

上述方法的作用刚好与startMonitoringItem方法相反,直到CLLocationManager终止监控和检索活动。

现在,已经创建了开始和终止方法,是时候用它们了!开启监控的正确时机是在用户为列表添加新的item对象的时候。

看一下ItemsViewController中的saveItem(_:),该unwind segue转场是在用户点击AddItemViewControllerSave按钮时触发,它同时生成了一个监控区域。在这个方法中找到调用persistItems()的那行,在它之前的一行添加如下代码:

startMonitoringItem(newItem)

当用户保存一个item对象时,就会激活这个监控。同样的,当启动app时,app从NSUserDefaults加载已存的item对象,这也就意味着启动的同时就需要开启对应的区域监控。

ItemsViewController .swift中,找到loadItems(),在for循环中添加如下代码:

startMonitoringItem(item)

这会确保每个item区域都处于受监控状态。

还有,你需要关注一下从列表中删除item。找到tableView(_:commitEditingStyle:forRowAtIndexPath:),在itemToRemove之后添加如下代码:

stopMonitoringItem(itemToRemove)

当用户删除表视图某行时,就会调用这个委托方法。现有的代码处理的是将该行对象从数据模型和视图上删除,刚刚加的代码将会终止对item监控。

此时此刻,你已经完成了很多事情!app已经有开启和终止对制定iBeacon的监控。

这个阶段,可以启动并构建app了;但是尽管已经注册的iBeacon在你的app检索范围内,可是目前app无法对发现iBeacon设备作出任何反馈......还需要继续完善它!

发现iBeacon的反馈


既然location manager已经监控iBeacon,是时候通过CLLocationManagerDelegate的某些方法对iBeacon作出反应。

首先也是最重要的是错误处理,因为你正在处理设备指定的硬件信息,想知道监控或者检索失败的任何可能原因。

ItemsViewController.swift里的CLLocationManagerDelegate的类extension添加下面两个方法:

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)")
}

这些方法对监控iBeacon时接收的所有错误信息做一个简单记录。

如果app一切运行正常,你不会看到这些方法的输出信息。然而,如果某些地方出了问题,很有可能从错误日志中获得很有价值的信息。

下一步就是实时显示iBeacon与iOS设备之间的距离。在CLLocationManagerDelegate的类extension里,实现如下委托方法:

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
      }
    }
  }
}

这个委托方法会在iBeacon进入监控范围,或者移除监控范围,或者iBeacon辐射范围发生改变的时候被调用。

该app的目的就是使用由上述委托方法提供的iBeacon数组来更新列表的item对象,并显示它们与设备的感应距离。重复迭代beacons数组,之后再迭代items数组,查看是两者之间是否有匹配的部分。稍后来处理TODO部分代码。

打开item .swift,给Item类添加如下属性:

dynamic var lastSeenBeacon: CLBeacon

该属性存储的是最后一个与之匹配的CLBeacon对象,用来显示距离信息。它有一个dynamic修饰符,以便于稍后对它使用key-value observation。

Item.swift底部,在类定义的外面,添加==操作符判断如下代码:

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)))
}

该等号操作符函数比较CLBeaconItem对象,检查它们是否相等--即,它们所有的标识是否匹配。这种情形下,如果UUID,major和minor值全部相同,那么CLBeacon与Item对象相等。

现在继续完成检索的委托方法,调用上述辅助方法。打开ItemsViewController.swift,回到locationManager(_:didRangeBeacons:inRegion:)。替换for循环里的TODO部分,如下:

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

这里,当发现一个item与iBeacon匹配时,把它赋值给lastSeenBeacon。你会发现item和iBeacon受益于之前等号操作符函数!

是时候使用该属性来显示监测到的iBeacon设备与iOS设备之间的距离。

打开ItemCell.swift,在didSet属性观察者起始部位,添加如下代码:

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

当为cell设置item时,同样要为lastSeenBeacon添加一个观察者。为了保持平衡,还要在cell已经设置过了item时,删除该观察者。为didSet添加一个willSet属性观察者。确保它属于item属性:

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

这会确保只有一个item 对象被观察。

当然,当cell被废弃时,同样需要删除观察者。还在ItemCell.swift,添加如下代码:

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

既然正在观测距离的变化,你就可以在iBeacon的距离发生变化的时候通过一些逻辑规则来作出反馈。

每个CLBeacon对象都有一个proximity属性,它是一个包含Far,Near,Immediate和Unknown的枚举。

ItemCell.swift中,为Core Location添加导入的申明:

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"
  }
}

该发放返回一个易读的远近值,后面会用到它。

接着,添加如下代码:

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) {
  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发生改变时,都会调用这个方法,它会用CLBeaconproximity值和accuracy值设置cell的detailTextLabel.text属性。

后面这个accuracy的值即使你的iOS设备和iBeacon没有移动,由于无线电频率的缘故,也会一直浮动,因此不要指望它来达到beacon的精确定位。

现在确保已经注册了iBeacon,然后让你的iOS设备逐渐靠近它,或者远离它。当你移动时,你会看到标签也会随之更新,如下:

11.jpg

你会发现proximityaccuracy值受到iBeacon位置的影响比较剧烈;如果把它放在类似箱子,包之类的东西里,信号就会受到阻碍,这是因为iBeacon是低功耗设备,它的信号很容易被减弱。

记住这点,在设计app时,需要把iBeacon放置在最妥善的位置上。

推送


app看上去已经很棒了;能显示iBeacon设备,并且还能实时监控它们的距离。但是这还不是app的终极目标。当app没有处于运行状态时,用户忽略了他们的手提包,或者宠物猫跑丢了--更有甚者,猫和手提包都不翼而飞了!

zorro-ibeacon-250x250.jpg

他们是不是好可怜?

此刻,你可能注意到为app添加iBeacon功能不需要太多代码。当猫猫和手提包都不见了时,添加一个推送也一样简单!

打开AppDelegate .swift,导入CoreLocation,如下:

import CoreLocation

接着,让AppDelegate遵循CLLocationManagerDelegate协议,在AppDelegate .swift底部添加如下代码(在类结束符下面)

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

在这之前,你需要初始化location manager,设置它的delegate

AppDelegate添加一个locationManager属性,用CLLocationManager对象实例化它:

let locationManager=CL LocationManager()

然后在application(_:didFinishLaunchingWithOptions:):添加如下代码:

locationManager.delegate=self

要知道app中所有的location manager都能通过startMonitoringForRegion(_:)共同监控你添加的区域(location manager是单例)。因此最后一步,只需要在走出某个区域时,对Core Location何时唤醒app作出反应就行了。

AppDelegate.swift底部的类extension里添加如下代码:

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就会调用上述方法,这是app一大亮点。假如离手提包越来越近,app就不需要提醒你,只有离它太远才会触发。

首先需要确定区域是否是CLBeaconRegion,因为当执行地理区域监控时,它还有可能是CLCircularRegion。然后用"Are you forgetting something?"消息发一个本地推送。

在iOS 8之后,app使用本地推送或者远程推送,必须注册推送的类型。系统给用户权限来限制不同类型推送的界面显示。假如app不能使用这些推送类型,即使它们是在推送载荷被指定过,系统也不会对app icon标记,不会显示提示信息,或者没有提示音效。

application(:_didFinishLaunchingWithOptions:):的最上面添加如下代码:

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

这段代码就是当app收到一个推送时,就会显示一段提示信息,并且播放一段音效。

构建项目;确保app能监测到至少一个iBeacon设备,点击Home按钮让app进入后台模式--这是现实生活中的场景,当你在处理其他事情的时候,比如处理Ray Wenderlich的另一个app时,希望这个后台app能通知你。现在远离iBeacon,一旦离得足够远,有就会收到这个推送,如下:

notification-281x500.jpg

锁屏上的通知

备注:苹果系统以未公开的方式延迟退出推送。这样设计可能方便当你在区域范围的边缘游荡或者iBeacon信号被干扰时,app不会接收之前的推送。以笔者的经验来说,在iBeacon离开区域范围一分钟时推送就会退出。

更进一步?


还没有为你的代码绑定iBeacon吗?从这儿下载最终的项目(here),教程所说的全在这里。

你已经有了一款很有用的app,来监控哪些比较难追踪的东西。加一些额外的思考和编程功底,你还可以给app添加更多有用的功能:

  • 通知用户那个iBeacon移出了监控范围

  • 重复推送,确保用户能看到它

  • 提醒用户iBeacon何时又返回监控范围

这篇iBeacons教程仅仅只是揭开iBeacon所有功能的冰山一角。

iBeacon不局限于传统app;你还可以在Passbook中使用它。比如,当你去看电影时,可以提供Passbook通行证当作电影票。当顾客走到附近有iBeacon设备的检票员面前时,app自动在iPhone上显示电影票!

对本篇教程有任何疑问或者意见,或者你有与iBeacon相关的好点子,欢迎加入我们的讨论!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多