WWDC Quick Look 💓 By SwiftGGTeam
Build location-aware enterprise apps

Build location-aware enterprise apps

观看原视频

Highlight

Apple 以 Caffè Macs 企业 App 为例,展示了如何用用户默认地点、Core Location 的 iBeacon 监控与测距、DateFormatter 和 NumberFormatter,把园区级定位体验扩展到全球员工场景。

核心内容

Caffè Macs 在 Apple Park 里承担的是员工餐厅入口。员工用 iPhone App 浏览菜单、下单、用 Apple Pay 支付;访客用 iPad kiosk 点餐;厨房再通过 iPad 上的 Kitchen Display System 接单和通知取餐。这里的定位需求很具体:员工打开 App 时,应该看到自己要去的那家 Caffè 的菜单。

难点来自园区环境。主 Caffè 和附近的 coffee station 距离很近,一个人可能同时接近多个餐饮点。如果 App 只用粗粒度位置,很容易把菜单切到错误地点,订单也会被送到用户没打算去的柜台。

Apple 的处理顺序很克制。先让员工选择默认地点,给出 “Closest to Me” 和具体 Caffè 两种入口;只有用户需要自动匹配附近地点时,才进入 Core Location 授权流程。这样,即使员工拒绝位置权限,App 仍然能按用户偏好打开指定菜单。

当 App 真正需要现场体验时,它使用 Core Location 和 iBeacon。region monitoring 先判断附近是否有目标 beacon,beacon ranging 再根据距离判断最近的位置。最后,DateFormatter、NumberFormatter 和本地化布局把同一套企业体验带到不同地区,避免时间、金额和阅读语言在全球员工场景里出错。

详细内容

先用用户偏好建立默认地点

03:28)演讲先处理一个容易被忽略的问题:别急着弹定位权限。Caffè Macs 允许员工选择 “Closest to Me”,也允许员工直接指定某一家 Caffè。后者完全不需要定位权限,适合用户预先拒绝或暂时不想授权的情况。

// Storing the user's preference using UserDefaults

UserDefaults.standard.set(defaultLocation.id, forKey: "defaultLocationId")

let defaultLocationId = UserDefaults.standard.integer(forKey: "defaultLocationId")

关键点:

  • defaultLocation.id 是用户选择的默认地点,适合放进 UserDefaults 这类小型持久化存储。
  • defaultLocationId 在下一次启动时读回,用来决定首页展示哪一家 Caffè 的菜单。
  • 这一步发生在定位服务之前,所以它也是定位授权失败时的 fallback。

请求刚好够用的位置权限

06:14)Caffè Macs 只请求 “When in Use” 授权。演讲明确说,“Always” 授权允许后台访问位置,但这个 App 的场景不需要它。企业 App 应该只请求功能所需的数据,并用 Info.plist 的 purpose string 解释原因。

// Add NSLocationWhenInUseUsageDescription to your Info.plist
// e.g. "Location is required for placing orders while using the app."

locationManager.requestWhenInUseAuthorization()

func locationManager(
    _ manager: CLLocationManager,
    didChangeAuthorization status: CLAuthorizationStatus) {

    switch status {
    case .restricted, .denied:
        disableLocationFeatures()

    case .authorizedWhenInUse, .authorizedAlways:
        enableLocationFeatures()

    case .notDetermined:
        // The user hasn't chosen an authorization status
    }
}

关键点:

  • NSLocationWhenInUseUsageDescription 必须存在,否则授权请求会立即失败。
  • requestWhenInUseAuthorization() 要放在用户执行相关任务时调用,避免用户看不懂弹窗来源。
  • didChangeAuthorization 是权限状态变化的入口,用户在系统设置里关闭权限时也会触发。
  • .restricted.denied 走降级路径,回到前面的默认地点偏好。

先确认设备支持 beacon 能力

07:02)定位服务依赖硬件能力。演讲要求在使用某项定位服务之前,先调用 CLLocationManager 的能力检查方法。这样 iPad kiosk、旧设备或受管设备缺少相关能力时,App 还有可控的替代路径。

if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
    // Supports region monitoring to detect beacon regions
}

if CLLocationManager.isRangingAvailable() {
    // Supports obtaining the relative distance to a nearby iBeacon device
}

关键点:

  • isMonitoringAvailable(for:) 检查能否监听 beacon region 的进入和离开。
  • isRangingAvailable() 检查设备能否估算附近 iBeacon 的相对距离。
  • 任一能力不可用时,App 不应该把用户卡在现场定位流程里。

用 region monitoring 找到附近 beacon

08:54)iBeacon 支持分两步:先用 region monitoring 发现附近是否存在匹配 beacon。部署 beacon 硬件时,需要写入 proximity UUID、major 和 minor。演讲建议用这些值表达层级,例如 Caffè 层级或某个 food station。

// Stage 1: Region Monitoring

func monitorBeacons() {
    if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {

        let constraint = CLBeaconIdentityConstraint(uuid: proximityUUID)

        let beaconRegion = CLBeaconRegion(
            beaconIdentityConstraint: constraint,
            identifier: beaconID
        )

        self.locationManager.startMonitoring(for: beaconRegion)
    }
}

关键点:

  • CLBeaconIdentityConstraint 用 proximity UUID 描述要匹配的 beacon 集合。
  • CLBeaconRegion 把约束和业务 identifier 绑定起来。
  • startMonitoring(for:) 注册后,系统会在进入或离开匹配区域时通知 delegate。
  • 调用前需要先创建 CLLocationManager 并设置 delegate。

用 beacon ranging 判断最近地点

09:30)进入 region 后,App 再启动 ranging。ranging 提供更细的相对距离信息,适合解决同一园区里多个餐饮点距离接近的问题。

// Stage 2: Beacon Ranging

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    guard let region = region as? CLBeaconRegion,
        CLLocationManager.isRangingAvailable()
        else { return }

    let constraint = CLBeaconIdentityConstraint(uuid: region.uuid)
    manager.startRangingBeacons(satisfying: constraint)
    beaconsToRange.append(region)
}

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {

}

关键点:

  • didEnterRegion 表示系统已经检测到匹配 beacon。
  • guard 同时确认 region 类型和 ranging 能力。
  • startRangingBeacons(satisfying:) 开始获取附近 beacon 的距离信息。
  • beaconsToRange 记录当前测距对象,方便后续按需停止。

10:09)收到 ranging 结果后,Caffè Macs 取最近的 beacon,并用 major、minor 和 proximity 判断要展示哪个地点的信息。

// Stage 2: Beacon Ranging

func locationManager(
    _ manager: CLLocationManager,
    didRangeBeacons beacons: [CLBeacon],
    in region: CLBeaconRegion) {

    guard let nearestBeacon = beacons.first else { return }
    let major = CLBeaconMajorValue(truncating: nearestBeacon.major)
    let minor = CLBeaconMinorValue(truncating: nearestBeacon.major)

    switch nearestBeacon.proximity {
    case .near, .immediate:
        displayInformation(for: major, and: minor)

    default:
        handleUnknownOrFarBeacon(for: major, and: minor)
    }
}

关键点:

  • beacons.first 代表当前结果里最接近的 beacon。
  • majorminor 用来映射业务对象,例如某一层、某个 Caffè 或某个 station。
  • .near.immediate 才展示地点信息,距离未知或较远时走保守处理。
  • 如果多个 beacon 共享相同 UUID、major 和 minor,演讲提醒结果可能只能通过 proximity 和 accuracy 区分。

把地点体验扩展到全球员工

11:32)位置还有宏观层面。员工分布在不同地区时,同一个取餐时间不能用单一格式展示。DateFormatter 负责把 Date 转成用户区域习惯下的文本。

// Formatting Dates
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
dateFormatter.string(from: Date())
// "Jun 25, 2020 at 9:41 AM"

关键点:

  • dateStyle 控制日期部分的展示粒度。
  • timeStyle 控制时间部分的展示粒度。
  • string(from:) 会依据 formatter 配置生成用户可读文本。
  • Caffè Macs 的 pickup window 场景需要正确的本地化时间。

12:41)金额格式也不能只看设备 locale。演讲里的 kiosk 示例显示,用户 locale 和业务地点可能不同;德州餐厅的订单金额应按美元配置,而不是误用其他地区货币。

// Configuring the Format of Currency
let formatter = NumberFormatter()
formatter.currencyCode = "CAD"
formatter.numberStyle = .currency
formatter.string(from: amount)
// "CA$1.00"

关键点:

  • numberStyle = .currency 使用预定义货币格式。
  • currencyCode 使用 ISO 4217 三字母货币代码,例如 CAD
  • NumberFormatter 只负责格式化,不负责货币换算。
  • 全球企业 App 通常要同时考虑用户 locale 和业务地点的货币规则。

核心启发

1. 园区点餐 App 的默认地点

做什么:为公司餐厅、健身房或班车 App 增加默认地点设置。

为什么值得做:本 session 的 Caffè Macs 先用用户偏好决定首页状态,定位权限失败时仍然能展示正确地点。

怎么开始:用 UserDefaults 保存地点 ID;首页启动时先读偏好;只有用户选择 “Closest to Me” 时才进入 Core Location 流程。

2. 近距离设施识别

做什么:在仓库货架、楼层入口或会议室门口部署 beacon,让 App 自动识别员工所在区域。

为什么值得做:region monitoring 和 beacon ranging 能解决多个地点距离很近时的识别问题。

怎么开始:为 beacon 规划 proximity UUID、major、minor;用 CLBeaconIdentityConstraintCLBeaconRegion 监听区域;进入区域后调用 startRangingBeacons 获取相对距离。

3. 隐私优先的位置入口

做什么:给企业 App 增加手动选择地点的入口,并把位置权限作为增强功能。

为什么值得做:演讲强调员工自己决定是否授权位置服务,App 仍需优雅处理拒绝、受限和临时授权。

怎么开始:在授权前展示业务选择;Info.plist 写清楚用途;在 didChangeAuthorization 中把 .restricted.denied 映射到手动地点模式。

4. 全球员工的时间和金额展示

做什么:为跨地区员工 App 统一处理取餐时间、预约窗口、订单金额和报销金额。

为什么值得做:DateFormatter 和 NumberFormatter 能减少手写字符串带来的地区格式错误。

怎么开始:日期用 DateFormatter 配置 dateStyletimeStyle;金额用 NumberFormatter.currency 样式,并显式设置业务所需的 currencyCode

5. 多语言企业界面验收

做什么:把企业 App 的本地化布局纳入发布前检查。

为什么值得做:演讲结尾把国际化作为全球企业体验的一部分,用户界面文件会生成 .strings 占位文本供翻译。

怎么开始:先抽取用户可见文案;用 Xcode 的本地化工具生成 .strings;用更长的语言和从右到左语言检查布局弹性。

关联 Session

评论

GitHub Issues · utterances