Highlight
iOS 15 让 Nearby Interaction 支持兼容的第三方 UWB 配件,开发者可以用 NINearbyAccessoryConfiguration 从配件获取精确距离和方向更新,解决蓝牙等通道难以判断近距离空间关系的问题。
核心内容
你做了一个智能音箱、工具箱标签或展厅装置。用户走近 3 米时,App 想显示基础控制;走进 1.5 米时,App 想打开更精确的操作界面。
以前很难做稳。蓝牙可以负责连接和传输数据,但它不适合判断用户在房间里的精确位置。网络连接也能交换状态,却无法告诉 App 用户站在配件的哪个方向。
Nearby Interaction(附近交互)在 iOS 14 已经能让两台搭载 U1 芯片的 iPhone 交换距离和方向信息。问题是,第三方配件无法直接加入这个会话。
iOS 15 补上了这个缺口。Apple 新增 NINearbyAccessoryConfiguration,让兼容的第三方 UWB(Ultra Wideband,超宽带)硬件可以和 iPhone 的 U1 芯片互操作。
这个能力不替代蓝牙或网络。App 仍然需要一条数据通道,把配置数据在 iPhone 和配件之间传来传去。Nearby Interaction 负责 UWB 测距会话,数据通道负责启动和协调会话。
配件先生成 Accessory Configuration Data。App 收到后创建 NINearbyAccessoryConfiguration 并启动 NISession。随后系统通过 delegate 生成 Shareable Configuration Data,App 要尽快把它发回配件。两边配置完成后,App 就会收到 NINearbyObject 更新,其中包含距离,硬件支持时还包含方向。
详细内容
1. 权限从一次性授权改为使用期间授权
(02:59)iOS 14 的 Nearby Interaction 权限每个 App 生命周期只允许一次。用户下次重新进入相关流程时,可能又会看到提示。
(03:34)iOS 15 的提示改为“使用 App 期间”授权。系统会在 App 第一次运行 NISession 时自动弹出提示。用户接受或拒绝后,提示不会再次出现。之后,用户可以在 Settings 中修改权限。
<key>NSNearbyInteractionUsageDescription</key>
<string>用于在你靠近配件时显示距离相关控制。</string>
关键点:
NSNearbyInteractionUsageDescription是 Info.plist 中的用途说明。- 系统会把这段文字展示在 Nearby Interaction 权限提示里。
- 第一次运行
NISession的时间点要对应明确的用户动作,例如“连接配件”或“开始查找”。 - 权限不足时,
NISession会因为权限相关错误失效,App 要解释原因并引导用户去 Settings 修改。
2. 用配件数据创建 NINearbyAccessoryConfiguration
(08:43)第三方配件会先通过蓝牙、本地网络或安全的互联网连接,把 Accessory Configuration Data 发给 App。这个数据由兼容 U1 的 UWB 硬件按规定格式生成。
(10:45)App 收到数据后,用它创建 NINearbyAccessoryConfiguration。如果数据格式无效,初始化会抛出错误。
import Foundation
import NearbyInteraction
final class AccessoryStore {
private var namesByToken: [NIDiscoveryToken: String] = [:]
func setupAccessory(configurationData: Data, name: String) throws -> NINearbyAccessoryConfiguration {
let configuration = try NINearbyAccessoryConfiguration(data: configurationData)
namesByToken[configuration.accessoryDiscoveryToken] = name
return configuration
}
func name(for token: NIDiscoveryToken) -> String? {
namesByToken[token]
}
}
关键点:
import NearbyInteraction引入 Nearby Interaction 框架。setupAccessory(configurationData:name:)接收配件发来的配置数据和配件名称。try NINearbyAccessoryConfiguration(data:)校验数据格式,并生成配件会话配置。configuration.accessoryDiscoveryToken是框架填充的配件发现标识。namesByToken把发现标识和配件名称关联起来,后续收到NINearbyObject更新时可以显示正确的配件名称。- 方法返回
NINearbyAccessoryConfiguration,调用方可以用它启动NISession。
3. 启动 NISession,并把 Shareable Configuration Data 发回配件
(13:02)App 创建配置后,接着创建 NISession、设置 delegate,并调用 run(_:)。
(13:19)配件也需要来自 Nearby Interaction 的 Shareable Configuration Data。系统会通过 iOS 15 新增的 delegate 回调把它交给 App。App 要尽快通过原来的数据通道发给配件,延迟过大会导致会话超时。
import Foundation
import NearbyInteraction
protocol AccessoryConnection {
func send(_ data: Data)
}
final class AccessorySessionController: NSObject, NISessionDelegate {
private let session = NISession()
private let configuration: NINearbyAccessoryConfiguration
private let connection: AccessoryConnection
init(configuration: NINearbyAccessoryConfiguration, connection: AccessoryConnection) {
self.configuration = configuration
self.connection = connection
super.init()
session.delegate = self
}
func start() {
session.run(configuration)
}
func session(
_ session: NISession,
didGenerateShareableConfigurationData shareableConfigurationData: Data,
for object: NINearbyObject
) {
connection.send(shareableConfigurationData)
}
}
关键点:
AccessoryConnection抽象数据通道,可以由蓝牙、局域网或其他安全连接实现。NISession()创建 Nearby Interaction 会话。session.delegate = self让控制器接收框架回调。session.run(configuration)用配件配置启动会话。didGenerateShareableConfigurationData提供 Shareable Configuration Data。connection.send(shareableConfigurationData)把数据原样发回配件。for object: NINearbyObject指明这份数据属于哪个配件,多个配件并行时很关键。
4. 处理超时,并在配置仍有效时重试
(15:25)如果 Shareable Configuration Data 没有及时送达配件,会话可能超时。Nearby Interaction 会通过 didRemove delegate 回调通知 App。
(15:44)App 可以检查移除原因。如果原因是 .timeout,并且 App 判断配件仍在附近,可以用同一个配置重新运行 session。缓存配置只在配件端 session 没有终止时有效。
import NearbyInteraction
final class RetryController: NSObject, NISessionDelegate {
private let session: NISession
private let configuration: NINearbyAccessoryConfiguration
private var retryCount = 0
init(session: NISession, configuration: NINearbyAccessoryConfiguration) {
self.session = session
self.configuration = configuration
super.init()
}
func session(
_ session: NISession,
didRemove nearbyObjects: [NINearbyObject],
reason: NINearbyObject.RemovalReason
) {
guard reason == .timeout else { return }
guard shouldRetry() else { return }
retryCount += 1
session.run(configuration)
}
private func shouldRetry() -> Bool {
retryCount < 3
}
}
关键点:
didRemove nearbyObjects表示一个或多个附近对象被移除。reason == .timeout对应会话超时场景。shouldRetry()放置 App 自己的判断逻辑,例如重试次数和配件是否通知停止。retryCount += 1记录当前重试次数,避免无限重试。session.run(configuration)用同一个配置再次启动会话。- 如果配件端 session 已终止,App 需要重新获取 Accessory Configuration Data,再走完整启动流程。
5. 用距离更新驱动区域功能
(19:24)iPhone 和配件都配置完成后,App 会通过 didUpdate 收到 NINearbyObject 更新。更新包含距离,硬件支持时还包含方向。
(20:26)演讲中的场景定义了两个区域:1.5 米半径和 3 米半径。用户进入 3 米区域时启用功能 A,进入 1.5 米区域时启用功能 B。Apple 建议对距离做平滑,避免用户突然移动或站在边界时 UI 快速跳变。
import NearbyInteraction
final class DistanceZoneController: NSObject, NISessionDelegate {
private var lastSmoothedDistance: Float?
func session(_ session: NISession, didUpdate nearbyObjects: [NINearbyObject]) {
guard let object = nearbyObjects.first else { return }
guard let distance = object.distance else { return }
let smoothedDistance = getSmoothedDistance(distance)
if smoothedDistance < 1.5 {
enableFunctionalityB()
} else if smoothedDistance < 3.0 {
enableFunctionalityA()
}
}
private func getSmoothedDistance(_ distance: Float) -> Float {
guard let previous = lastSmoothedDistance else {
lastSmoothedDistance = distance
return distance
}
let alpha: Float = 0.3
let smoothed = alpha * distance + (1 - alpha) * previous
lastSmoothedDistance = smoothed
return smoothed
}
private func enableFunctionalityA() {
// 用户进入 3 米区域后的功能。
}
private func enableFunctionalityB() {
// 用户进入 1.5 米区域后的功能。
}
}
关键点:
didUpdate nearbyObjects是距离和方向更新入口。nearbyObjects.first取出本次更新的配件对象。object.distance是框架提供的距离,单位是米。getSmoothedDistance(_:)对距离做平滑处理,减少边界附近的 UI 抖动。smoothedDistance < 1.5对应更近的区域,触发功能 B。smoothedDistance < 3.0对应较大的区域,触发功能 A。- 多个配件并行时,可以为每个配件创建并运行一个 session。
核心启发
-
做什么:给智能音箱增加“靠近显示控制面板”。 为什么值得做:Nearby Interaction 可以提供与配件的精确距离,App 不必依赖蓝牙信号强度猜测用户是否走近。 怎么开始:让音箱通过蓝牙发送 Accessory Configuration Data,App 用
NINearbyAccessoryConfiguration(data:)启动NISession,在didUpdate中用 3 米阈值显示控制面板。 -
做什么:给仓库工具标签做“近距离确认”。 为什么值得做:UWB 距离适合判断用户是否真的站在目标配件旁边,减少误操作。 怎么开始:为每个标签维护
accessoryDiscoveryToken到名称的映射,在object.distance小于 1.5 米时显示“确认取用”按钮。 -
做什么:做一个展厅 App,用户靠近展品时自动切换内容。 为什么值得做:演讲中的 1.5 米和 3 米区域模型可以直接变成展品讲解层级:远处展示摘要,近处展示操作。 怎么开始:每个展品配一个兼容 UWB 配件;App 为每个配件创建 session;在
didUpdate中按距离阈值切换页面状态。 -
做什么:给配件连接流程增加可恢复的超时重试。 为什么值得做:Shareable Configuration Data 发送太慢会导致 session 超时,重试能减少用户重新连接的次数。 怎么开始:实现
session(_:didRemove:reason:),当reason == .timeout且重试次数未超限时,用缓存的NINearbyAccessoryConfiguration再次调用session.run(_:)。 -
做什么:在 App 中设计清晰的 Nearby Interaction 权限入口。 为什么值得做:iOS 15 只在第一次运行
NISession时弹出权限提示,时机不清楚会降低用户授权概率。 怎么开始:把session.run(_:)放在“开始查找配件”按钮之后,在 Info.plist 的NSNearbyInteractionUsageDescription中说明距离感知用途。
关联 Session
- Design for spatial interaction — 学习如何把距离和方向转化为用户能理解的空间交互。
- Explore UWB-based car keys — 了解 UWB 如何在车钥匙场景中完成接近、解锁和个性化。
- Meet the Location Button — 对比 iOS 15 中低摩擦的位置授权设计。
- Connect Bluetooth devices to Apple Watch — 参考蓝牙配件连接方式,理解 Nearby Interaction 所需的数据通道。
评论
GitHub Issues · utterances