When geofences are not triggering on iOS, the cause is almost always one of a short list: the app lacks Always authorization, the device was already inside the region when you added it, the radius is too small for iOS to detect a crossing, the app hit the 20 region limit, or the test did not wait long enough. iOS geofencing works well, but it reports crossings on its own schedule. This guide explains how region monitoring decides when to fire, and how to debug a Capacitor app using @capgo/background-geolocation,它包括 iOS 和 Android 原生地理围栏功能
iOS 区域监测原理
在 iOS 中,地理围栏是 CLCircularRegion 对象注册到 Core Location。 一旦注册,操作系统就会监控它们,而不是您的应用程序。 您的应用程序可以被挂起或不运行,iOS仍然会跟踪区域,使用蜂窝塔、Wi-Fi和GPS来降低电池使用量。
当 iOS 确定设备穿过边界时,它会将进入或退出事件传递给应用程序,必要时在后台启动应用程序,并且如果允许的话。 几个规则控制了这个决定:
- iOS 不会立即报告设备穿过边界。 苹果的指导是设备必须穿过,移动一个最小距离超过边界,并且在事件被传递之前至少停留 20 秒钟。这可以避免因 GPS jitter 而触发。
- 区域监控的位置精度取决于周围的无线电信号。在一个拥挤的城市中,它可以很快。在一个农村公路上,它可能需要几分钟。
- 事件仅用于过渡。 在监视开始时位于区域内不算作进入。
在一个 Capacitor 应用程序中设置地理围栏
安装插件并配置 iOS 密钥:
bun add @capgo/background-geolocation
bunx cap sync
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to detect when you arrive at a store.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need Always access to notify you when you arrive, even when the app is closed.</string>
然后注册区域:
import { BackgroundGeolocation } from '@capgo/background-geolocation';
await BackgroundGeolocation.setupGeofencing({
url: 'https://api.example.com/v1/geofence-events',
headers: { Authorization: 'Bearer <token>' },
notifyOnEntry: true,
notifyOnExit: true,
payload: { userId: 'u-42' },
});
await BackgroundGeolocation.addGeofence({
identifier: 'store-paris-11',
latitude: 48.8606,
longitude: 2.3795,
radius: 150,
payload: { storeId: 'paris-11' },
});
await BackgroundGeolocation.addListener('geofenceTransition', (event) => {
console.log(`${event.transition} ${event.identifier}`, event.payload);
});
await BackgroundGeolocation.addListener('geofenceError', (event) => {
console.error('Geofence error', event.identifier, event.code, event.message);
});
setupGeofencing 要求地理围栏所需的权限(默认值)(requestPermissions 默认值 true. On iOS means Always.
现在,失败模式。
1. 你没有 Always 权限
iOS 的区域监控需要 Always 权限。只有 While Using App 权限,区域事件在应用程序处于后台或未运行时不会被触发,这是地理围栏的整个目的。
在注册区域之前检查,并在自己的 UI 中解释请求:
const status = await BackgroundGeolocation.checkPermissions();
if (status.backgroundLocation !== 'always' && status.backgroundLocation !== 'granted') {
// Show your own screen explaining the feature, then:
const next = await BackgroundGeolocation.requestPermissions({
permissions: ['backgroundLocation'],
});
if (next.backgroundLocation === 'when_in_use' || next.backgroundLocation === 'denied') {
await BackgroundGeolocation.openSettings();
}
}
注意,iOS 可能不会立即显示 Always 升级提示。选择“允许 While Using App”用户可能只会看到第二个提示。选择“允许一次”用户会获得一个临时许可,会重置。如果地理围栏是核心功能,请在每次应用启动时检查状态,并在它不是 Always 时显示一个清晰的横幅。
2. 设备已经在区域内
如果在手机内添加一个地理围栏,iOS 不会发送进入事件。没有任何交叉。团队在测试时经常遇到这个问题,测试时会在办公室周围添加一个区域。
在应用中修复它:当您注册一个区域时,比较当前位置与区域并处理“已经在内”自己。
import { Geolocation } from '@capacitor/geolocation';
function distanceMeters(lat1: number, lon1: number, lat2: number, lon2: number) {
const R = 6371000;
const toRad = (d: number) => (d * Math.PI) / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.asin(Math.sqrt(a));
}
const region = { identifier: 'store-paris-11', latitude: 48.8606, longitude: 2.3795, radius: 150 };
await BackgroundGeolocation.addGeofence(region);
const pos = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });
if (distanceMeters(pos.coords.latitude, pos.coords.longitude, region.latitude, region.longitude) < region.radius) {
handleAlreadyInside(region.identifier);
}
declare function handleAlreadyInside(id: string): void;
要测试进入事件,请走或开车远离半径,等待,然后回来。
3. 半径太小
iOS 接受小半径,但在 100 米以下的可靠性会迅速下降,因为区域监控依赖于 Wi-Fi 和细胞定位。一个 30 米的商店门口的区域可能会延迟、在顾客离开后触发或根本不会触发。
实用值:
| 用例 | 建议半径 |
|---|---|
| 商店或餐厅到达 | 100到200米 |
| 办公室、校园、仓库 | 150到300米 |
| 社区或城市区域 | 500米及以上 |
如果您需要门店级精度,请结合较大的地理围栏和前台位置或一次用户进入的信标。Capgo还具有 室内近距离 iBeacon插件
4. 您已达到20个区域限制
一个iOS应用程序可以同时监控最多20个区域。当您添加更多区域时,注册失败,会出现监控错误。
使用 getMonitoredGeofences() 来查看实际注册的内容,并保留最近区域的滚动集合:
const { regions } = await BackgroundGeolocation.getMonitoredGeofences();
console.log(`Monitoring ${regions.length} regions`, regions);
一个常见的模式:
- 将您的所有地点保存在服务器上。
- 当应用程序启动时,和当用户移动到远处(例如,离开一个大型“刷新”区域)时,获取19个最近的地点。
- 调用
removeAllGeofences()然后添加19个最近的地点加上一个大型区域,中心在用户位置。离开这个大型区域就是刷新的信号。
安卓允许每个应用程序最多100个地理围栏,所以code在安卓上可以正常工作的内容可能会在iOS上失败的原因就是这个。
5. 测试时间太短或太静止
iOS只在设备明显跨越并停留在另一侧后才会报告过渡。如果您以50公里每小时的速度通过150米的区域,您会花费大约20秒在其中。可能不够。
当测试时:
- 在室外待一分钟,然后进去并至少待几分钟。
- 测试时请打开Wi-Fi。关闭Wi-Fi会使区域监控变得不准确,即使您没有连接到任何网络。
- 不要依赖模拟器来测试时间。使用Xcode中的GPX路线来检查您的处理程序,然后在设备上测试。
6. 事件已触发,但您的JavaScript未运行
当iOS在后台重新启动您的应用以响应区域事件时,原生插件会接收到它,但Capacitor WebView可能尚未加载或可能会被快速挂起。 geofenceTransition 监听器仅在WebView处于活跃状态时才会触发。
这就是为什么 setupGeofencing 接受一个 url。原生code将转换为POST请求,包括您的合并 payload,因此您的后端即使JavaScript未运行也会收到它。头部会在设置时持久化,因此在重新启动后仍然有效,您可以使用 updateHeaders().
以最佳努力方式进行交付,且无重试队列。请确保端点快速且幂等,并在测试期间记录每个请求。
7. 位置服务或后台应用刷新功能是关闭的
检查测试设备上的这些设置:
- 设置、隐私和安全、位置服务已开启。
- 您的应用设置为始终。
- 您的应用启用了精确位置。没有它,区域会变得不靠谱。
- 低功耗模式在第一次测试期间是关闭的。它会减少后台活动。
8. 重启和应用更新
iOS会在应用启动时恢复监控区域。重启设备后,监控会在系统恢复后重新启动,但您的应用应该在启动时重新同步区域:与您期望的进行比较并重新添加缺失的区域。同样,在应用更新后以及服务器更改区域列表时也要做同样的事情。 getMonitoredGeofences() 在Android上,地理围栏在重启后会被删除,并且在其他情况下(位置关闭,应用数据清除)也会被删除,所以在启动时重新注册是必须的。这样做在两种平台上都保持了逻辑的简单性。
9. 强制退出
如果用户从应用切换器中强制退出应用,iOS会将其视为用户不希望应用运行的强烈信号。iOS版本之间的区域重新启动行为发生了变化。不要构建一个仅在应用重新启动后强制退出后才能正常工作的功能,并在您的用户运行的iOS版本上测试它。
检查测试设备上的这些设置:
调试清单
| 检查 | 如何 |
|---|---|
| 始终启用权限 | checkPermissions() 返回 always 或 granted 如何 backgroundLocation |
| 区域已注册 | getMonitoredGeofences() 列出标识符 |
| 少于 20 个区域 | 计数从 getMonitoredGeofences() |
| 半径 ≥ 100 米 | 检查 addGeofence calls |
| 不在此区域 | 在注册时比较当前位置 |
| 错误已记录 | 听取 geofenceError |
| 服务器接收事件 | Set url in setupGeofencing in |
| and log requests | Real movement test |
Android 的区别需要注意
如果相同的code在 Android 上工作但在 iOS 上不工作,20 个区域限制和 Always 要求是常见的原因。如果它在 iOS 上工作但在 Android 上不工作,请检查您是否添加了 ACCESS_BACKGROUND_LOCATION 到您的清单并设置 backgroundLocation: true 在 setupGeofencing.该插件不会为您添加该权限,因为 Google Play 需要为后台位置的声明。
发布的变化
权限字符串和原生配置需要新构建。区域列表、半径值和 UI 文本可以在服务器端或通过 Capgo实时更新 进行。对于持续跟踪而不是进入和退出事件,请参见为什么 iOS 停止后台位置更新 ,并查看该插件的 为获得完整的API。