iOS14苹果推出了NearbyInteraction
框架, 用于感知和连接具有U1芯片的设备。其主要目的是空间感知(近距离定位)。
Nearby Interaction
简介Nearby Interaction
主要提供了两种信息, 距离(Distance)和方位(Direction)。 当两个设备通过Nearby Interaction
互相连接时, 他们会不断发送距离和方位信息, 这样就能互相定位了。 并且同一个设备能够和周围的多个设备建立连接,互不干扰
1. 导入框架
2. 创建session
let session = NISession() // token print(session.discoveryToken) |
session
有一个discoveryToken
属性, 两台设备互相交换token
才可以进行连接。 创建session后,我们读取token, 然后通过MultipeerConnectivity
框架进行token的交换。
3. 一个完整的流程启用session
let session = NISession() session.delegate = self let myDiscoveryToken = session.discoveryToken // 发送token到需要连接的设备上 sendDiscoverTokenToMyPeer(peer, myDiscoveryToken) // 使用接收到的token let peerDiscoverToken = "xxxxx" // 配置 let config = NINearbyPeerConfiguration(peerToken: peerDiscoverToken) // 启动session session.run(config) |
1. 数据更新都是通过此方法回调
optional func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]) |
2. 当设备没有一段时间没有更新或者对方断开连接会收到此回调,需要注意的是这个回调并不是肯定会被调用,系统只会尽可能进行通知
optional func session(_ session: NISession, didRemove nearbyObjects: [NINearbyObject], reason: NINearbyObject.RemovalReason) |
3. 生命周期相关回调
// 比如app进入后台,这个方法会回调 optional func sessionWasSuspended(_ session: NISession) // 我们只有等此方法回调了才能继续使用session optional func sessionSuspensionEnded(_ session: NISession) // 此方法回调后我们只能重新创建session进行连接 optional func session(_ session: NISession, didInvalidateWith error: Error) |
当连接多个设备的时候,我们需要为每一个连接的设备创建
session
NINearbyObject
open class NINearbyObject : NSObject, NSCopying, NSSecureCoding { // token @NSCopying open var discoveryToken: NIDiscoveryToken { get } // 距离单位是米 public var distance: Float? { get } // 方位 public var direction: simd_float3? { get } } |
检测距离大约是一个锥形,在这个方位内距离和方位都能正常更新。 超过方位距离会更新,但是方位可能不会更新。 然后我们需要确保手机是竖向的。
https://developer.apple.com/videos/play/wwdc2020/10668/
https://juejin.cn/post/7085521868320407582
基于 Session 10165 探索与第三方硬件的近距离交互
2021 年 4 月的发布会上,AirTag 横空出世,从此妈妈再也不用担心我找不到钥匙了。UWB 这项技术也慢慢走进了人们的视野。现在不仅仅是只有官方的钥匙扣了。第三方的硬件授权设备也能与苹果进行交互了。 本文是讲了苹果在 Nearby Interaction 框架上的更新,全文主要分为三部分: 首先带你快速的了解一下之前是如何使用 Nearby Interaction 框架的,然后讲述在 iOS 15 上关于用户访问权限的改进,最后根据最新的 api 来实现一个简单的 demo。
class ViewController: UIViewController, NISessionDelegate { override func viewDidLoad() { super.viewDidLoad() let token = _token let niSession = NISession() niSession.delegate = self niSession.run(NINearbyPeerConfiguration(peerToken: token)) } func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]){ nearbyObjects.forEach { (niNearbyObject) in print(niNearbyObject.distance!); print(niNearbyObject.direction!); } } } |
如何在两个手机之间建立联系呢?首先创建一个 NISessions 实例,然后在代码中遵守 NISessionDelegate 协议,在 session 实例的 run 函数中需要传入一个 NIConfiguration 的子类做配置对象。如果想要在两个 iPhone 之间建立联系,可以使用 NINearbyPeerConfiguration 做参数,但是在 NINearbyPeerConfiguration 的初始化中需要知道对方的 token,这个 token 可以通过网络进行传输,当你准备好这一切的时候,Nearby Interaction 将开始为程序提供 NearbyObject 更新流,每个更新包含到附近设备的距离和方向(可选)。如果想要继续深入的了解这个库的 API,可以去看去年演讲 "Meet Nearby Interaction"。
在 iOS 14 上的的 Nearby Interaction 的权限流程是这样的,当你的 App 在一个新的生命周期中,第一次创建一个 NISession 会话的时候会展示弹窗,弹窗的样式如下图,但由于此权限是一次性的,因此在某些情况下也可能导致展示多次弹窗。
今年在 iOS 15 上对权限进行了改进,弹窗如下图。这是 iOS 15 中新的“附近交互”权限提示。看起来很相似,但却和之前的权限授予流程有以下不同:
总结一下新的 Nearby Interaction 用户权限模型。在弹窗中点击 Allow 后,您的应用将获得在应用使用过程中使用 Nearby Interaction 的持久权限。弹窗上将显示一个使用说明字符串,在 Info.plist 中配置。在这个配置中要简洁明了的解释您在应用程序中访问附近的设备可以提供那些有趣的能力。在第一次也是最后一次出现提示后,您的应用的名称和图标将出现在“设置”中,这意味着用户可以随时更改您的应用的权限状态。当应用程序没有足够的权限使用 Nearby Interaction 时, NISessions 所有的功能都会失效。因此,如果应用程序中的关键功能依赖于对附近设备的访问,请务必向用户清楚地解释这一点,并在适当的时候引导他们使用“设置”去打开配置权限。
没有业务场景的新特性都是瞎逼逼,下面将从一个实用的业务场景出发来介绍如何使用 Nearby Interaction API 与第三方配件一起工作。
想象一下,在你的设备周围定义了一个半径为 1.5 米的区域和另外一个半径为 3 米的更大的区域,当用户进入较大的区域的时候您希望启动功能 A,当用户进入较小的区域的时候你期望启动功能 B,如何使用 Nearby Interaction 来实现这一个功能?
Nearby Interaction 需要硬件与应用程序之间具有数据交换的能力。至于采用何种技术实现数据交换,这完全取决于硬件设备具有什么样的能力。假设设备已经支持蓝牙。因为您将能够利用现有的蓝牙功能来进行数据交换。如果硬件设备连接到了本地网络或互联网中,完全可以使用网络进行数据交换,在应用程序和配件之间可以互相传输数据是下一步需要执行的操作的必要条件。
上面的回顾中我们在了两部 iPhone 之间启动会话时创建了 NearbyPeerConfiguration。如果要开始与硬件的会话,需要创建 NearbyAccessoryConfiguration。这是 iOS 15 中新的 NIConfiguration 类型。如果要实例化一个 NearbyAccessoryConfiguration。需要为它提供一些配置数据,系统接受的一种特定的用来描述这个设备的数据格式。但是我们如何获得这个配置数据,这个特定的格式是什么?与 U1 兼容的硬件设备(认证过的供应商)将知道如何根据请求生成此配置数据。这意味着您在硬件设备本身上运行的代码需要生成此数据然后通过 part1 的数据通道将其发送到您的应用程序。代码如下:
private func setupAccessory(_ configData: Data, name: String) { do { config = try NINearbyAccessoryConfiguration(data: configData) } catch let error { print("Bad config data from accessory \(name). Error: \(error)") return } // 保存token cacheToken(config!.accessoryDiscoveryToken,accessoryName:name) } |
setupAccessory 是我在应用程序中编写工具方法。每当我从硬件设备获取到配置数据时,都应该调用此方法。现在,我可以使用收到的数据创建 NINearbyAccessoryConfiguration 。尽量在 do/catch 语句中创建这个配置。这样做的好处是如果数据无效的话,NIConfiguration 的 init 方法会抛出异常。如果配置对象创建成功的话,就说明从硬件传输过来的数据格式是正确的。创建配置的最终目的是使用它来运行会话。与硬件设备进行交互。
现在,我们已经准备好与这个硬件进行交互了。为了管理交互,需要创建一个 NISession 实例,并设置他的代理,在启动会话时,需要在 NISession 实例的 run 函数中传入上面创建好的 Configuration。就像 Nearby Interaction 需要来自硬件的配置数据一样,硬件设备也需要来自 Nearby Interaction 的配置数据才能知道如何配置自己。此数据需要采用名为“可共享配置数据”方式。当您使用 Configuration 运行会话时,Nearby Interaction 将通过代理让你的应用程序提供可共享的配置数据。就像我们使用数据通道接收配件的配置数据一样,在这里,我们将再次使用数据通道将共享的配置数据发送回配件。代码如下:
func session(_ session: NISession,didGenerateShareableConfigurationData: Data, for object: NINearbyObject) { // 通过工具方法 获取数据链接 guard let conn = getConnection(object: object) else { return } //发送共享数据 conn.sendSharedableConfigurationData(data) } |
didGenerateShareableConfigurationData 是 iOS 15 中的新回调。该回调提供了可共享的配置数据,并指示它应该去哪个硬件设备,这在与多个硬件设备交互时非常有用。应尽快的通过数据通道将数据发送到硬件。一般而言,管理与不同硬件的数据连接可以采用多种不同的形式,这一切都取决于需求。为简单起见,假设在我的应用程序中,我选择让每个与我交互的配件保持独立的数据连接。为了让我的代码看起来井井有条,我定义了一个工具函数,它根据我给它的 NearbyObject 返回给我一个连接。获得对连接的引用后,我将使用它将可共享配置数据发送到硬件设备。
如果 ShareableConfigurationData 发送得不够快,您的会话可能会超时。代码如下:
func session(_ session: NISession, didRemove nearbyObjects: [NINearbyObject], reason: NINearbyObject.RemovalReason) { // 只有超时才进行后续操作 guard reason == .timeout else { return } // 获取硬件设备 guard let accessory = nearbyObjects.first else { return } //是否应该重试 是否config 还在有效的状态 if shouldRetryWithCachedConfig(accessory) { if let config = session.configuration { session.run(config) } } } |
与硬件的会话超时将通过 didRemove 的代理返回给应用程序。当 Nearby Interaction 给我 didRemove 的回调时,我将首先检查移除的原因。如果原因是 timeout,并且我非常确信硬件设备可能仍在附近,可以尝试进行重联。为了决定此附件是否应该进入“重试流程”,我定义了一个辅助函数,其中包含帮助我做出此决定的专门逻辑。诸如“我重试了多少次没有成功?”这样的情况。或“配件是否通知我它已停止?” 或其他类似问题可以成为这个决策函数中的一部分。如果我决定重试,我所要做的就是使用相同的配置再次运行会话。请记住,缓存配置仅在硬件设备上的的会话未终止时才保持有效。如果会话终止的话就需要重新建立连接了。请记住,硬件设备上的会话是设备上运行的代码必须管理的事情,并且可以通过多种不同方式完成,所有这些都取决于应用的场景。
接收到可共享的配置数据后,配件上的超宽带硬件将立即开始以适当的配置运行,以便与应用程序中的 NISession 进行交互。如果配件和运行应用程序的 iPhone 彼此靠近,会话将开始为您的应用程序提供 NearbyObject 更新流,其中包含距离和到硬件的方向。甚至可以通过为每个硬件创建和运行会话来同时与多个硬件设备互动。根据配件上的硬件功能,您还可以获得等效的邻近更新在配件上运行的代码中。从框架中获得 NearbyObject 更新后,您将如何处理它们?提醒一下,我们希望构建这样一种体验:当用户进入较大的区域时,应用程序和配件启用功能 A ,而当用户进入配件周围的较小区域时启用功能 B。代码如下:
func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]){ guard let accessory = nearbyObjects.first else { return } guard let distance = accessory.distance else { return } let smoothedDistance = getSmoothedDistance(distance) if smoothedDistance < 1.5 { doA(smoothedDistance); } else if smoothedDistance < 3.0 { doB(smoothedDistance) } } |
代码中展示了如何在 iOS 应用程序中使用 NearbyObject 更新来执行此操作。当应用程序和硬件之间的会话正在运行时,有关硬件的更新将通过 didUpdate 进行回调。首先,我将获取框架为我们提供的附近对象的引用。接下来,我将创建一个带有到该对象的距离的局部变量,框架以米为单位提供距硬件的距离。接下来我要做的是将这些数据提供给我的应用程序中名为 getSmoothedDistance 的工具函数。我在我的应用程序中定义了这个函数来帮助我防范处理距离的各种异常。例如,在用户突然移动时,或者他们恰好站在区域之间的边界上。最后,我可以检查用户与配件的距离是否超过了我预定义的阈值。来根据当前距离选择启用 Function A 或 Function B。
Nearby Interaction 在 iOS 14 上还只能是手机与手机之间进行交互,但在 iOS 15 上扩展到了手机与第三方的硬件。不断增强了 Nearby Interaction 框架的能力。后续在智能家具,AR/MR,室内定位,钱包,地铁检票都应该都会有广泛的应用。