ARKit教程17_第十三章:位置跟踪和信标

正文

在前三章中,我们学习了如何使用ARKit实现虚拟广告牌,该虚拟广告牌首先是通过扫描矩形显示出来,然后是扫描QR码显示出来。

广告牌的第一个版本使用视图控制器和附加到ARKit平面材料的视图来显示图像轮播和视频播放器。

在第二个修订版中,我们使用故事板替换了视图控制器并添加了Web视图;这使得在ARKit会话中显示网站成为可能。我们还学习了切换全屏模式。

在本章的最后一章中,我们将学习如何使用位置功能,通过在用户靠近感兴趣的地方时自动启用功能来增强用户体验。

具体来说,我们将学习到:

  • 获取用户的位置。

  • 使用地理围栏来检测用户何时进入或离开特定区域。

  • 使用信标进行接近检测。

开始

确定用户位置

虽然我们可能没有使用它,但我们可能听说过Core Location,用于处理设备位置和信标的iOS框架。我们将在本项目中实现它。

启用核心位置

要使用Core Location并遵守用户权限要求,我们需要向Info.plist文件添加一些Key

使用Core Location有两种方法:

  • when in use: Core Location仅在应用程序位于前台时才向应用程序发送位置更新。

  • always: Core Location随时向应用程序发送位置更新,无论应用程序是在前台还是后台。

在本项目中,我们需要使用always

iOS 11中已经发生了一些变化。在iOS 10及更早版本中,要求always模式,我们需要将NSLocationAlwaysUsageDescription键添加到Info.plist,其值应包含提示用户让用户决定是否同意。

另一方面,在iOS 11中,我们需要包含两个Key,要求始终和何时使用权限。

我们首先按照下图作如下操作:

文件以源代码模式打开。在文件中看到的第一个<dict>标签后立即转到第5行,然后插入以下两行:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key><string>Chapter13 requires access to your phones location to notify when you enter a geofence containing ads</string><key>NSLocationWhenInUseUsageDescription</key><string>Chapter13 requires access to your phones location to notify when you enter a geofence containing ads</string>

iOS要求访问设备位置的权限时,这些行指定提示给用户的文本。

注意:XML代码已经过格式化,通过在两个<string> </ string>标记中包含的文本中添加几个换行符来提高可读性。当文本显示给用户时会呈现这些换行符,因此在编写代码时忽略它们会更好,如果使用复制和粘贴则删除。

Info.plist中正确设置密钥非常重要。如果Core Location没有找到它所期望的密钥,那么它就不会告诉你它们已经丢失了。虽然它会向Xcode控制台发出警告,但它会默默地拒绝执行任何与位置相关的活动。

打开LocationManager.swift;这实现了LocationManager类。我们将在本章的过程中添加更多功能。但是现在,在初始化程序之后添加此方法:

func initialize() {// 1 locationManager.delegate = self // 2 locationManager.activityType = .otherNavigation// Geofencing requires always authorization // 3 locationManager.requestAlwaysAuthorization() // 4 locationManager.startUpdatingLocation()}

上面代码作用如下:

  • 1: LocationManagerCore LocationCLLocationManager类的实例。该类公开了一个根据该应用的需求量身定制的界面。

  • 2: Core Location需要知道位置跟踪的目的,以便更好地满足应用程序的需求。像如下应用场景:automotive navigationfitnessother navigationother。你在这个应用程序中使用otherNavigation

  • 3: 这要求用户获得永久授权的许可。如果未授予或拒绝授权,则授权过程通过委托异步执行,因此下一行不会产生任何影响。

  • 4: 此行通过指示Core Location发送位置更新来启动跟踪过程,除非权限被拒绝。

现在我们需要添加下面的方法:

private var locationManager = LocationManager()

找到viewDidLoad()方法。在将场景设置为sceneView之后,添加以下代码行:

// Initialize core location locationManager.initialize() locationManager.delegate = self

这将调用初始化方法并设置位置管理器委托。

在文件的最后,我们添加一个extension:

// MARK: - LocationManagerDelegate extension ViewController: LocationManagerDelegate {// MARK: Location func locationManager(_ locationManager: LocationManager, didEnterRegionId regionId: String) { }func locationManager(_ locationManager: LocationManager, didExitRegionId regionId: String) { }// MARK: Beacons func locationManager(_ locationManager: LocationManager, didRangeBeacon beacon: CLBeacon) { }func locationManager(_ locationManager: LocationManager, didLeaveBeacon beacon: CLBeacon) {     }}

LocationManagerDelegate是一个类似于CLLocationManager委托的自定义委托协议。我们可以将其视为CLLocationManagerDelegate的包装器,它在LocationManager类中使用。

实际上,LocationManager将实现CLLocationManagerDelegate中定义的总共九种方法,其中四种将通过自定义LocationManagerDelegate转发到ViewController

现在,我们需要在采用CLLocationManagerDelegate的扩展中的LocationManager中实现两个委托方法。这是第一个:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {    print("Authorization status changed to: \(status)")}

当授权状态从默认的notDetermined更改为任何其他可能的值时,将调用此方法:restricteddeniedauthorizedAlwaysauthorizedWhenInUse

第二个:

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {    print("Location manager failed with error: " + "\(error.localizedDescription)"}

如果在尝试检索位置时发生错误,则会调用此方法。实现此方法很重要,或者Core Location在尝试使用位置服务时会抛出异常。

添加这两个委托方法后,构建并运行应用程序。核心位置将要求我们授权该应用访问我们的位置。

标题下方的描述是我们之前添加到Info.plist的文本。请务必选择Always Allow

地理围栏

现在已设置Core Location,我们可以开始跟踪设备位置。不关心实际的设备位置。当我们在三章前介绍时,你会发现它应该能够在用户离目标一定距离时显示出来。此功能称为地理围栏,它包括监视某个位置周围的区域,特别是当设备跨越该区域的边界时。

项目中有一个Geo Fencing的按钮。其目的是启动或停止区域监测。

开始区域监测

ViewController.swift中,找到toggleLocationTracking();这已经连接到Geo Fencing按钮的tap事件。该方法包含注释的打印语句;暂时忽略它们。

在方法的开头添加此代码:

isLocationTrackingActive = !isLocationTrackingActiveif isLocationTrackingActive { } else { }

isLocationTrackingActive是指示位置跟踪当前是否处于活动状态的标记。你将填充if /else语句。

print语句移动到if分支中并取消注释。它们用于向控制台显示警告,提醒我们RMK位置是硬编码的。

仍在if分支内部,在print语句之前,添加以下代码:

// 1 self.toggleLocationTrackingButton.setImage(#imageLiteral(resourceName: "arKit-fence-on"), for: .normal)// 2 let rmkLocation = Constants.razewareMobileKioskLocation// 3let rmkCoordinates = rmkLocation.location

当用户点击地理围栏按钮,并且正在激活位置跟踪时:

  • 1: 我们更新按钮图像以指示它已打开。

  • 2: 我们可以读取RMK所在目标点的位置。

  • 3: 我们阅读该位置的坐标。

RMK位置使用包含名称和位置的自定义结构在Constants.swift中进行静态硬编码。

现在你有了目标位置。下一步是开始监视该目标周围的区域。在print语句之后,添加以下代码:

do {    try locationManager.startMonitoring(         location: rmkCoordinates,         radius: Constants.geofencingRadius,         identifier: Constants.razewareMobileKioskIdentifier)} catch (let error as LocationManager.GeofencingError) {      print( "An error occurred while monitoring a region: \(error)") } catch (let error) {     print( "Tracking location error: \(error.localizedDescription)") }

startMonitoring()开始监视半径为Constants.geofencingRadius米的rmlCoordinates位置周围的区域,当前设置为300,并在Constants.razewareMobileKioskIdentifier中指定一个字符串标识符。

监控一个地区

监视启动在LocationManager类中完成。打开LocationManager.swift。在initialize方法之后,添加以下内容:

func startMonitoring(location: CLLocationCoordinate2D, radius: Double, identifier: String) throws {// 1guard CLLocationManager.isMonitoringAvailable( for: CLCircularRegion.self)else { throw GeofencingError.notSupported }guard CLLocationManager.authorizationStatus() == .authorizedAlwayselse { throw GeofencingError.notAuthorized }trackedLocation = location// 2let region = CLCircularRegion(center: location, radius: radius, identifier: identifier)// 3region.notifyOnEntry = true region.notifyOnExit = true// 4locationManager.startMonitoring(for: region)// 5// Delay state request by 1 second due to an old bug // http://www.openradar.me/16986842DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), qos: .default, flags: []) {    self.locationManager.requestState(for: region)    }}

上面代码作用如下:

  • 1:我们首先需要检查:
     ̯a: 可以使用区域监视,因为某些设备可能不支持它。
     ̯b: 用户授权始终开启位置跟踪。

  • 2: 这是我们要监控的区域。它以位置为中心,并具有由radius参数指定的半径。

  • 3: 我们可以启用这两个文件来指示我们有兴趣在设备进入和退出受监控区域时通知的Core Location

  • 4: 这开始了区域监测。

  • 5: 在这里,我们可以请求通过Core Location委托异步传送的区域的立即状态。

注意:区域是共享的应用程序资源,这意味着如果我们有多个CLLocationManager实例,则每个实例都会在进入或退出任何受监视区域时收到通知。

此外,对状态的请求包含在延迟调用中,因为旧的错误导致该方法在同步调用时不执行任何操作。

现在,我们需要提供在设备进入和退出受监控区域时由Core Location调用的两个委托方法。

LocationManager中,转到底部。在最终扩展中,添加进入区域时调用的委托方法:

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {    if let region = region as? CLBeaconRegion { } else {    print("Entered in region: \(region)")   delegate?.locationManager(   self, didEnterRegionId: region.identifier) }}

我们现在可以忽略if分支;它将在以后使用。 else分支是区域输入事件被转发到LocationManagerDelegate中定义的委托作为locationManager(_:didEnterRegionId :)并在ViewController中实现。虽然它现在已经空了,但我们很快就会照顾到它。我们将区域标识符传递给该方法。

didEnterRegion之后,添加其镜面反射didExitRegion方法:

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {     print("Left region \(region)")     delegate?.locationManager( self, didExitRegionId: region.identifier) }

这类似于通过调用locationManager(_:didExitRegionId :)来通知何时设备离开受监视区域。

注意:我们可以在一台设备上监控多达20个区域。

当我们对requestState做一个延迟调用(for :)时,请记住startMonitoring(location:radius:identifier :)吗?结果通过另一个CLLocationManagerDelegate方法异步传递。在locationManager(_:didFailWithError :)之后添加它:

func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {    switch state {     // 1     case .inside:    locationManager(manager, didEnterRegion: region)    // 2     case .outside, .unknown:        break     }}

上面代码作用如下:

  • 1: 在这里,我们有兴趣了解设备何时已经位于受监控区域内,在这种情况下,我们将委托给locationManager(_:didEnterRegion :),就好像它是从受监控区域外部到内部的正常过渡一样。

  • 2: 在这里,你说你对其他案件并不感兴趣。

你在开始监控之后立即调用requestState(for :)的原因是我们想知道 - 在那个特定的时刻 - 如果设备已经在被监控区域内。区域监视仅在状态发生变化时触发事件,即从外部转换到内部,反之亦然。如果我们已经在室内或室外,它将不会触发任何事件。

如果设备已在内部,那么我们需要对信标执行某些操作。稍后会详细介绍!

还有一个委托方法可以实现,至少在调试方面是有用的。在locationManager之后添加它(_:didDetermineState:for :)

func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {    print("Geofencing monitoring failed for region " + "\(String(describing: region?.identifier))," + "error: \(error.localizedDescription)")}

startMonitoring(for :)失败时调用此方法。将错误消息打印到控制台可能有助于我们了解问题何时发生以及发生的原因。

对区域变化做出反应

当设备穿过受监控区域时,Core Location会通知LocationManager。反过来,LocationManager使用LocationManagerDelegate协议的两种方法将通知转发给ViewController。我们将使用这两个通知来玩信标。但是,我们可以在用户进入某个区域时提醒用户。

ViewController.swift中,滚动到locationManager(_:didEnterRegionId :)并添加以下代码:

// Notify the user that he’s entered the geofenced zone let distance = String(format: "%0.0f", Constants.geofencingRadius)let message = "\(regionId) is less than \(distance) meters. " + "Come say hi and interact with our e-billboard"let title = regionIdshowAlert(with: "geofencing-notification", title: title, message: message)

这会提示警报并通知用户RMK在附近。

停止区域监测

当我们点击Geo Fencing按钮时,到目前为止编写的代码负责启动区域监视并通知设备何时穿过区域。要关闭圆圈,我们必须在相反的方向上工作:停止区域监控。

转到toggleLocationTracking()点击处理程序。早些时候,如果未实现,你就离开了最外层的else分支。使用以下代码填充该空白区域:

// 1 self.toggleLocationTrackingButton.setImage( #imageLiteral(resourceName: "arKit-fence-off"), for: .normal) // 2 locationManager.stopMonitoringRegions()

上面代码作用如下:

  • 1: 恢复地理围栏按钮的关闭图标。

  • 2: 停止区域监控。

但是,该停止方法尚不存在。打开LocationManager.swift并在startMonitoring()之后添加它:

func stopMonitoringRegions() {     trackedLocation = nil    for region in locationManager.monitoredRegions {        locationManager.stopMonitoring(for: region) }}

我们无需跟踪已开始监控的区域;它们列在CLLocationManagermonitoredRegions属性中 - 我们只需遍历它们并停止每个属性。

距离计算

到目前为止,我们已经忽略了获取实际的设备位置。但是,这是通过实现单个委托方法完成的。

打开LocationManager并在扩展的开头添加此方法以实现CLLocationManagerDelegate

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {    // 1     guard let currentLocation = locations.last else { return }    // 2     guard let trackedLocation = trackedLocation else { return }    let location = CLLocation(latitude: trackedLocation.latitude, longitude: trackedLocation.longitude)    // 3     let distance = currentLocation.distance(from: location)    print("Distance: \(distance)")}

上面代码作用如下:

  • 1: 委托方法接收一个数组,该数组包含自上次调用以来的位置列表,按从最旧到最新的时间戳排序。你拿最后一个;即最近的。

  • 2: 我们获得包含目标位置的CLLocation - RMK。由于它们存储为CLLocationCoordinate2D结构,因此我们将转换为CLLocation的实例。

  • 3: 我们计算两个位置之间的距离并将其打印到Xcode控制台。

测试

我们写了很多代码。现在是时候运行应用程序来查看它的全部动态。为方便起见,选择模拟器,然后构建并运行应用程序。然后,点击Geo Fencing按钮。

除了按钮图标改变颜色,显然没有任何反应。如果尚未显示,请打开Xcode控制台。在执行toggleLocationTracking()时遇到的打印语句会打印出警告:

WARNING: ensure that the Razeware Mobile Kiosk is at a location near you, otherwise geofencing and beacon detection won't work The current location is: Pisa (43.7153187, 10.4019739)

如前所述,RMK位置已经静态地设置在比萨斜塔附近。我们可能想知道:我是否必须去那里测试应用程序?当然,请随意去,但是在这个项目中测试地理围栏的方法比较简单一些。

Xcode中,打开位于res组中的TestLocations.gpx。它应该如下所示:

<?xml version="1.0"?><gpx version="1.1" creator="Xcode">      <wpt lat="43.72281" lon="10.3958585">            <name>Lungarno, Pisa</name>            <time>2010-01-01T00:00:00Z</time>      </wpt>      <wpt lat="43.7153187" lon="10.4019739">        <name>Piazza dei Miracoli, Pisa</name>        <time>2010-01-01T00:00:30Z</time>       </wpt><wpt lat="43.72281" lon="10.3958585">  <name>Lungarno, Pisa</name>  <time>2010-01-01T00:01:00Z</time>    </wpt></gpx>

该文件包含由三个航点定义的路线:起点,目的地和返回起点。选择初始位置距离目的地约1,000米的距离,即RMK位置。

除了坐标和名称,每个航路点也有时间。绝对值的重要性并不重要,但相对差异表示从一个位置到下一个位置所需的时间。如果你仔细观察,你会注意到一个航点到下一个航点的差异为30秒。

Xcode中,我们可以通过调试区域中的“模拟位置”按钮选择此文件。这使得Xcode模拟.gpx文件中定义的路由。

如果你看一下Xcode中的控制台面板,你应该开始看到距离更新,如下所示:

Distance: 935.436896038748 Distance: 903.575340351579 Distance: 871.696596559219 Distance: 839.800076468077 Distance: 807.885154208941 Distance: 775.951162495185 Distance: 743.997388360152 Distance: 712.023068265481

每次Core Location为刚刚实现的方法locationManager(_:didUpdateLocations)提供新位置时,都会更新。

一旦距离低于我们正在监视的区域的半径,我们应该看到如下日志:

Entered in region: CLCircularRegion (identifier:'The Razeware Mobile Kiosk', center:<+43.71531870,+10.40197390>, radius:300.00m)

这证实了设备的模拟位置已进入地理围栏区域。如果稍等一下,当设备退出该区域时,我们会看到类似的消息。

Left region CLCircularRegion (identifier:'The Razeware Mobile Kiosk', center:<+43.71531870,+10.40197390>, radius:300.00m)

作为旁注,如果我们想使用iPhone和真实位置,我们可以这样做。只有一个建议:我们可能希望查看数据/ Constants.swift并在其中设置新位置,而不是要求Razeware移动信息亭移动到我们当前位置附近。

信标

既然我们可以检测到设备何时进入或退出某个区域,那么下一步是什么?我们可能已经注意到在进入时会向用户提示警报,但仅此一点不应该证明这么多工作是正确的。

这是信标发挥作用的地方。但他们是什么?信标是一小块硬件,可定期通过蓝牙LE(低能耗)发出信号。在最简单的化身中,信号由三个静态数据字段组成:Proximity UUIDMajorMinor

就这么简单。但这对于接近检测也很有用:我们的手机可以拦截信号,识别信标并触发向注册信标的应用程序发出通知。

注意:如上所述,ProximityMajorMinor是静态数据;然而,它们可以(并且应该)在购买信标后进行更改。更新数据的过程取决于供应商。
数据可用于任何有助于特定目的的方式;然而,一个典型的模式是使用邻近度来识别我们的公司或应用程序,一个部分的主要部分,如房间,建筑物,商店和未成年人,以区分同一部分中的信标。

信标是低能耗设备;出于这个原因,他们可以在一个电池上运行几年,不间断地每天24小时,365天 - 有时甚至366天 - 广播他们的数据。由于能量低,它们的信号范围有限。因此,它们通常用于近距离检测。

检测广播信号的设备可以用一致的近似来确定它与信标的距离。距离分为三个不同的范围:

  • Far:超过10米远。

  • Near: 在几米之内。

  • Immediate: 几厘米远。

检测信标

在本项目中,我们将使用信标来检测何时接近RMK,并触发自动QR码检测。在iOS中,信标由Core Location处理,这是一个我们应该已经相当熟悉的框架。

注意:要调试和测试信标, 我们需要一个信标。可以使用备用iPhone作为信标或真正的信标; App Store中有一些应用程序可让我们将iPhone用作灯塔。我们还需要另一部iPhone来测试应用程序。

信标检测不是发现过程。当任何信标出现在雷达上时,我们不会要求核心位置通知。

相反,我们要求Core Location告知我们何时检测到特定信标,由其接近,主要和次要标识,或者我们也可以在相同的邻近UUID或接近度和主要信号下请求一系列信标。关键是我们要求Core Location监控你已经知道的信标。

核心位置定义CLBeaconRegion类,用于指定要监视的信标。只有一个信标的区域已经在data/Constants.swift中定义,打包成一个名为razeadBeacons的数组。

我们应该将beacon标识替换为我们自己的数据。最好不要重复使用我们在代码中找到的相同身份,因此至少应该为我们的信标生成一个新的邻近UUID

管理信标监控

打开LocationManager.swift。在使用MARK: - Beacons标识的扩展中,添加以下两种方法:

// 1 func startMonitoring(beacons: [CLBeaconRegion]) {    for beacon in beacons {        startMonitoring(beacon: beacon)    } }// 2 func startMonitoring(beacon: CLBeaconRegion) {    guard CLLocationManager.isRangingAvailable() else {         print("[ERROR] Beacon ranging is not available")         return     }    locationManager.startMonitoring(for: beacon)}

上面的代码作用如下:

  • 1: 注册信标区域阵列。这循环遍历数组并将注册委托给单区域方法。

  • 2: 在检查该应用程序运行的设备上是否有该功能后,注册单个信标区域。

在此处,添加用于停止监视的方法:

func stopMonitoring(beacons: [CLBeaconRegion]) {     for beacon in beacons {         stopMonitoring(beacon: beacon)     } }func stopMonitoring(beacon: CLBeaconRegion) {     locationManager.stopMonitoring(for: beacon) }

我们必须在ViewController.swift中调用这些方法来分别启动和停止信标监视。我们将在下一部分中执行此操作。

当检测信标时,核心位置语言被称为测距,实际上有两个不同的阶段:

  • Entering the region: 当检测到属于该区域ID的第一个信标时发生。

  • Ranging a single beacon: 在单个信标范围内发生。

第一种情况是通过之前实现的locationManager(_:didEnterRegion :)委托方法处理的。如果还记得,你把if分支留空了。现在用以下代码填写:

print("Entered in beacon region: \(region)") locationManager.startRangingBeacons(in: region)

这会将消息打印到控制台并启动属于我们输入的区域的测距信标。

现在,当信标在范围内或发生错误时,剩下的就是通知。在最后一个LocationManager扩展的末尾,添加以下两个委托方法:

// MARK: Beacons // 1 func locationManager(_ manager: CLLocationManager,didRangeBeacons beacons: [CLBeacon],in region: CLBeaconRegion) {    for beacon in beacons {         delegate?.locationManager(self, didRangeBeacon: beacon)     }}// 2 func locationManager(_ manager: CLLocationManager,rangingBeaconsDidFailFor region: CLBeaconRegion, withError error: Error) {    print("Beacon ranging failed for region \(region) " + "with error: \(error.localizedDescription)")}

上面代码如下:

  • 1: 当信标数组中的一个或多个信标被测距时,调用此方法。每个信标都通过LocationManagerDelegate转发到ViewController

  • 2: 如果信标范围失败,则调用此方法。要保持一致,请向控制台输出错误消息。

最后一个修复的细节:在stopMonitoring(beacon :)中,我们调用了stopMonitoring(for :)。最好还停止属于该区域的测距信标,因此在方法开头添加此行代码,就在stopMonitoring之前:

locationManager.stopRangingBeacons(in: beacon)

手动停止区域和信标监控

还记得UI中的按钮以启动地理围栏吗?我们可以使用相同的按钮来停止它 - 这已经在其处理程序方法中完成。但是,需要进行一些清理。

ViewController.swift中,滚动到toggleLocationTracking()。在最顶层的if语句的else分支中,在locationManager.stopMonitoringRegions()之后,添加:

// 1 locationManager.stopMonitoring(beacons: Constants.razeadBeacons) // 2 beaconStatusImage.isHidden = true beaconStatusLabel.isHidden = true

上面的代码作用如下:

  • 1: 如果激活,则停止信标监视。

  • 2: 隐藏信标状态图标和标签。

消耗信标检测

现在,信标检测是一件事,我们可以触发自动QR扫描模式。目前,在ViewController中,当用户点击屏幕时,会运行QR代码扫描尝试。是时候修改这种行为了。这是你需要做的:

  • 保留点击以关闭视频播放器功能。

  • 将方法的其余部分(负责QR码检测)移动到新的scanQRCode()方法中。

touchesBegan(_:with)开头的if语句的右括号之后,添加以下两行:

// 1 } // 2 private func scanQRCode() {

上述代码作用如下:

  • 1: 关闭touchesBegun方法。

  • 2: 开始一个新的scanQRCode方法,它“继承”以前属于touchesBegun的代码。

编译项目以确保我们在正确的位置输入代码。

响应信标通知

在响应信标通知之前,我们应确保已开始测距。无需检查代码 - 我们尚未完成此操作。设备进入受监控区域后,本应用应启动测距信标,这将在ViewController中调用locationManager(_:didEnterRegionId :)委托方法时发生。

在该方法的末尾添加此行:

locationManager.startMonitoring( beacons: Constants.razeadBeacons)

要执行自动QR扫描,我们需要重复调用之前创建的scanQRCode()方法,并在后续调用之间保持一定的时间间隔。这个要求闻起来像一个计时器 - 这实际上就是你要使用的。 ViewController已经包含了处理计时器的大部分代码。我们需要将其与信标范围连接起来。

找到locationManager(_:didRangeBeacon :)委托方法;当新信标在范围内时调用它。添加此实现:

// 1 beaconStatusImage.isHidden = false beaconStatusLabel.isHidden = false// 2 switch beacon.proximity { case .immediate:    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-1") case .near:    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-2") case .far:    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-3") case .unknown:    beaconStatusImage.image = #imageLiteral(resourceName: "arKit-marker-4") }// Start auto scan, but only if the app is in the foreground // and there is no active billboard // 3 if UIApplication.shared.applicationState == .active && (billboard == nil || billboard?.hasBillboardNode == false) {    // 4     startAutoscanTimer()}

上面的代码作用如下:

  • 1: 默认情况下隐藏有一个图标和标签,用于报告信标测距状态。因为在这种委托方法中,信标已被范围化,使它们可见。

  • 2: 为图标选择合适的图像。

  • 3: 如果应用程序在前台,并且当前没有显示广告牌......

  • 4: ...然后启动计时器,启用自动QR检测。

相反,当灯塔离开时,我们需要做相反的事情。将以下实现添加到locationManager(_:didLeaveBeacon)

// 1 stopAutoscanTimer() // 2 beaconStatusImage.isHidden = true beaconStatusLabel.isHidden = true

上面代码作用如下:

  • 1: 这会停止计时器,禁用自动QR扫描。

  • 2: 这会隐藏信标状态图标和标签。

当用户退出该区域时,我们希望执行相同的操作。将相同的代码添加到locationManager(_:didExitRegionId :)

stopAutoscanTimer() beaconStatusImage.isHidden = true beaconStatusLabel.isHidden = true

最后一个位置应该取消定时器,当我们手动点击Geo Fencing按钮停止检测时。这由toggleLocationTracking()方法处理。找到它,并在else分支的底部,隐藏图标和标签后,添加以下代码行:

stopAutoscanTimer()

如果计时器处于活动状态,则会停止计时

定时器和QR码检测

当计时器为红色时,我们想要开始QR码检测尝试。找到didFireTimer(timer :)方法,只需添加:

scanQRCode()

测试

要测试信标,我们需要使用物理设备。我们可以使用启动项目附带的.gpx文件来模拟路径,从而可以测试设备何时进入和离开受监控区域。

我们还需要一个信标或备用iPhone,我们可以在其上安装将其变为信标的应用程序。在App Store中有几个这样的应用程序。

准备好后,运行应用程序,然后通过Xcode调试区域中的Simulate Locations按钮加载.gpx文件。等待虚拟位置距离RMK 300米范围内。

如果不想运行模拟路线,那么有两种选择:

  • 选项#1:手动重新配置应用以使用我们附近的真实位置:
    1: 我们可以使用Google地图找到坐标,这些坐标在我们将地图置于当前位置或至少靠近它之后显示在网址中。
    2: 获得坐标后,打开Constants.swift,然后将坐标替换为实际坐标。

  • 选项#2:通过.gpx文件避免模拟路由,但改为使用静态虚拟位置进行配置:
    1: 在Xcode中,找到res组中包含的TestLocations.gpx
    2: 右键单击它,然后选择在Finder中显示。
    3: 弹出时从Finder中选择.gpx文件,然后复制并粘贴,或右键单击文件并选择复制。
    4: 将重复的文件重命名为StaticLocation.gpx,或我们选择的任何其他名称。
    5: 在Xcode中,右键单击res组,选择Add files to Chapter13”,然后选择新创建的文件,并单击 Add 按钮。
    6: 打开文件并删除第一个和最后一个 <wpt> ... </ wpt>标签。
    7: 现在,我们可以在使用
    Xcode的模拟位置功能时选择此文件。这会将我们的设备放在RMK**旁边。

无论我们选择哪种方法,当运行应用程序,并且设备位于受监控区域内时,都会激活信标监控。

只要受监控的信标有范围,就应该看到启用自动检测按钮:

这表示正在进行自动QR扫描。此外,工具栏中央会显示一个图标,通知信标在范围内:

  • 绿色: 非常近

  • 橙色: 近

  • 红色: 远

  • 灰色: 未知

我们可以使用它来手动关闭广告牌。请注意,创建广告牌时,应用会自动停止地理围栏和自动检测。

如果无法对信标进行测距,请参阅以下列表:

  • 验证信标是否已打开。

  • 确认我们的iPhone已启用蓝牙。

  • 确认我们的iPhone已启用蓝牙。验证iPhone是否位于受监控区域内,即距离Constants.swift文件中指定的位置最多300米。

  • Xcode的控制台中,检查错误消息或警告。

  • 使用我们的信标供应商的应用程序(如果有)来验证信标是否在范围内。

  • 验证信标接近度,主要和次要数据是否与Constants.swift文件中指定的数据匹配。

(0)

相关推荐

  • 换个角度看蓝牙,物联网时代位置服务的先行者

    内容导读 今天主要分析一下蓝牙定位技术的工作原理与应用现状及相关企业的盈利模式,及规模化存在的难点.. 众所周知,蓝牙通讯技术被广泛的用于智能家居,手机,智能手表等可穿戴产品的通讯中.随着NB-IoT ...

  • 你家的WIFI安全吗?10块钱自制WIFI测试器(ESP8266)

    额--朋友们好啊,我是图吧老缺德人,今天咱简单说下三句话(划掉)教你10块钱让你的朋友们都掉线的操作,10块钱自制WIFI KILLER(ESP 8266 DEAUTHER). 其实咱以前虽然多年以前 ...

  • ARKit教程16_第十二章:高级用户交互

    正文 在之前的章节,我们学习了: 检测一个矩形. 检测一个QR码. 在检测到的矩形和QR码上面添加一个平面. 在平面上面显示内容. 在这一章,我们将会学到: 通过使用故事板而不是独立的视图控制器来改善 ...

  • 《广州棋坛六十年》第一百二十三章 苏天雄奋击董文渊

    第一百二十三章 苏天雄奋击董文渊 九月三日,六王争鼎赛的第三战,卢辉对方绍钦.他们二人在一九三五年的华东,华中队对华南比赛中,方绍钦以一胜一和击败卢辉.但比赛的积分,卢辉得五分居第四名,方绍钦得四分居 ...

  • 推十书 | 刘咸炘:《孟子·离娄篇》上十三章综义庚申

    <孟子·离娄篇>上十三章综义庚申 刘咸炘著 孔子述大学之道,修身齐家而国治天下平.曾子述以十章,子思又述其要.语日:人存政举,人亡政息.为政在人,取人以身.修身以道,修道以仁.仁者,人也, ...

  • 道德经第三十三章——知人者智

    这一章短小精悍 ,内容值得考究,值得记下. 下列出原文: 知人者智,自知者明:胜人者有力,自胜者强:知足者富,强行者有志:不失其所者久,死而不亡者寿. 老子在这一章提出了知人和自知,胜人和自胜.人贵自 ...

  • 『中医偏方精品』第十三章 月经疾病

    第十三章    月经疾病 一.痛经 凡月经前后或行经期发生腹痛等不适症状,从而影响生活.劳动者,称痛经. 痛经患者的症状以腹痛为主,多在月经来潮第1-2天出现,以下腹阵发性绞痛为多见;亦可出现胀痛.坠 ...

  • 『中医偏方精品』第二十三章 美容美发

    第二十三章  美容美发 一.减肥轻身 1茶消脂健美 用料 茶叶适量. 制法 沸水冲沏,待茶浓时饮用. 功效 消脂去腻,提精神.肥胖欲减肥之人,常饮有效. 2乌龙茶消脂益寿 用料 乌龙茶3克,槐角18克 ...

  • 宝利老师导读《苏东坡传》第十三章

    第十三章 本章有三个层次. 一.京城团聚. 二.徐州治水. 三.文坛第一. 苏轼从密州调离,本来是去山西的河中为官.古代官员任职都要回到京城述职.于是苏轼经过济南进京,但是没有能见到住在济南的弟弟,因 ...

  • 宝利老师导读《苏东坡传》第二十三章

    第二十三章 本章的主题词是"为民请命". 苏轼就像一个独行侠,一个人战斗,想要改变吏治,但却失败了.他也没能让朝廷采取好预防措施,解决大饥荒.但是这一章里写到的两年,终于拨开云雾见 ...

  • 王辉明:稀 奇十三章之挑干精

    东山有坪,名曰东山坪. 从窍角沱江边码头舍舟登岸,拾级而上,迎面有道横亘半山腰的青石坡,其广大不知好多里.辗转上得坡来,却是一片平地,有条长约数里的大街,就是大佛段正街.正街一头连着弹子石,一头通往大 ...