Wenn Geofences unter iOS nicht ausgelöst werden, liegt die Ursache fast immer an einem dieser Punkte: der App fehlt die „Always“-Berechtigung, das Gerät befand sich bereits in der Region, als sie hinzugefügt wurde, der Radius ist zu klein für eine iOS-Erkennung, die App hat das Limit von 20 Regionen erreicht oder der Test war nicht lang genug. iOS Geofencing funktioniert gut, meldet Überquerungen aber nach eigenem Zeitplan. Dieser Guide erklärt, wie das Region Monitoring entscheidet, wann es feuert, und wie man eine Capacitor App debuggt mit @capgo/background-geolocation, was natives Geofencing auf iOS und Android beinhaltet.
So funktioniert das iOS Region Monitoring
Unter iOS sind Geofences CLCircularRegion Objekte, die bei Core Location registriert sind. Einmal registriert, überwacht das Betriebssystem sie, nicht deine App. Deine App kann im Hintergrund pausiert oder gar nicht aktiv sein, und iOS trackt die Regionen trotzdem – durch eine Kombination aus Funkzellen, Wi-Fi und GPS, um den Akku zu schonen.
Wenn iOS feststellt, dass das Gerät eine Grenze überschritten hat, sendet es ein Enter- oder Exit-Event an die App und startet sie bei Bedarf im Hintergrund, sofern erlaubt. Ein paar Regeln steuern diese Entscheidung:
- iOS meldet eine Überschreitung nicht in dem Moment, in dem das Gerät die Grenze berührt. Laut Apples Vorgaben muss das Gerät die Grenze überqueren, eine Mindestdistanz hinter der Grenze zurücklegen und dort mindestens etwa 20 Sekunden bleiben, bevor das Event ausgelöst wird. Das verhindert Fehlalarme durch GPS-Jitter.
- Die Genauigkeit der Standortbestimmung für das Region-Monitoring hängt von der verfügbaren Funkabdeckung ab. In einer Stadt mit dichtem Wi-Fi-Netz geht es schnell. Auf einer Landstraße kann es Minuten dauern.
- Events beziehen sich nur auf Übergänge. Wenn man sich bereits in einer Region befindet, während das Monitoring startet, gilt dies nicht als Entry.
Geofencing in einer Capacitor-App einrichten
Installiere das Plugin und konfiguriere die iOS-Keys:
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>
Registriere anschließend die Regionen:
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 fragt standardmäßig nach der für Geofencing benötigten Berechtigung (requestPermissions ist standardmäßig true). Auf iOS bedeutet das „Immer“.
Kommen wir nun zu den Fehlerquellen.
1. Keine „Immer“-Berechtigung vorhanden
Region Monitoring benötigt auf iOS die „Immer“-Berechtigung. Mit nur „Beim Verwenden der App“ werden keine Events ausgelöst, wenn die App im Hintergrund oder nicht aktiv ist – was ja genau der Zweck eines Geofence ist.
Prüfe den Status, bevor du Regionen registrierst, und erkläre die Anfrage zuerst in deinem eigenen 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();
}
}
Beachte, dass iOS den Prompt für das Upgrade auf „Immer“ eventuell nicht sofort anzeigt. Nutzer, die „Beim Verwenden der App erlauben“ gewählt haben, sehen den zweiten Prompt erst später. Nutzer, die „Nur einmal erlauben“ gewählt haben, erhalten eine temporäre Berechtigung, die zurückgesetzt wird. Wenn Geofencing ein Kernfeature ist, prüfe den Status bei jedem App-Start und zeige einen deutlichen Banner an, wenn nicht „Immer“ ausgewählt wurde.
2. Das Gerät befand sich bereits in der Region
Wenn du einen Geofence hinzufügst, während das Telefon sich bereits darin befindet, sendet iOS kein „Enter“-Event. Es wurde ja nichts überschritten. Teams erleben das ständig beim Testen am Schreibtisch mit einer Region um das Büro herum.
Löse es in deiner App: Vergleiche beim Registrieren einer Region die aktuelle Position mit der Region und behandle den Fall „bereits innerhalb“ selbst.
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;
Um ein „Enter“-Event zu testen, geh oder fahr weit über den Radius hinaus, warte kurz und komm dann zurück.
3. Der Radius ist zu klein
iOS akzeptiert kleine Radien, aber die Zuverlässigkeit sinkt unter ca. 100 Metern rapide, da das Region Monitoring auf WLAN- und Mobilfunk-Positionierung angewiesen ist. Eine 30-Meter-Region um eine Ladentür kann zu spät, erst nachdem der Kunde gegangen ist, oder gar nicht auslösen.
Praxiswerte:
| Anwendungsfall | Empfohlener Radius |
|---|---|
| Ankunft im Store oder Restaurant | 100 bis 200 m |
| Büro, Campus, Lagerhalle | 150 bis 300 m |
| Viertel oder Stadtzone | 500 m und mehr |
Für Präzision auf Türebene kombiniere einen größeren Geofence mit Foreground Location oder einem Beacon, sobald der User im Bereich ist. Capgo bietet zudem ein iBeacon Plugin für Indoor-Proximity.
4. Du erreichst das Limit von 20 Regionen
Eine iOS-App kann maximal 20 Regionen gleichzeitig überwachen. Wenn du mehr hinzufügst, schlägt die Registrierung für die zusätzlichen Regionen fehl und es tritt ein Monitoring-Fehler auf.
Nutzt getMonitoredGeofences() um zu sehen, was tatsächlich registriert ist, und ein rollierendes Set der nächstgelegenen Regionen zu behalten:
const { regions } = await BackgroundGeolocation.getMonitoredGeofences();
console.log(`Monitoring ${regions.length} regions`, regions);
Ein gängiges Pattern:
- Speichere deine vollständige Liste an Orten auf dem Server.
- Wenn die App startet oder der User sich weit entfernt (z. B. beim Verlassen einer großen „Refresh“-Region um den User), rufe die 19 nächstgelegenen Orte ab.
- Rufe
removeAllGeofences(), füge dann die 19 nächsten plus eine große Region hinzu, die auf den User zentriert ist. Das Verlassen dieser großen Region ist dein Signal zum Refresh.
Android erlaubt bis zu 100 Geofences pro App, deshalb kann code, das auf Android funktioniert, allein aus diesem Grund auf iOS fehlschlagen.
5. Der Test war zu kurz oder zu statisch
iOS meldet Übergänge erst, wenn das Gerät die Grenze deutlich überschritten hat und auf der anderen Seite bleibt. Wenn du mit 50 km/h durch eine 150 Meter große Region fährst, verbringst du nur etwa 20 Sekunden darin. Das ist eventuell nicht genug.
Beim Testen:
- Bleib eine Minute draußen, geh dann rein und bleib dort mindestens ein paar Minuten.
- Teste mit aktiviertem Wi-Fi. Wenn Wi-Fi ausgeschaltet ist, wird das Region Monitoring deutlich ungenauer, selbst wenn du nicht mit einem Netzwerk verbunden bist.
- Verlass dich beim Timing nicht auf den Simulator. Nutze ihn mit einer GPX-Route in Xcode, um deine Handler zu prüfen, und teste danach auf einem echten Gerät.
6. Das Event wurde gefeuert, aber dein JavaScript wurde nicht ausgeführt
Wenn iOS deine App im Hintergrund für ein Region-Event neu startet, erhält das native Plugin das Event, aber die Capacitor WebView ist eventuell noch nicht geladen oder wird schnell wieder suspendiert. Der geofenceTransition Listener feuert nur, solange die WebView aktiv ist.
Deshalb setupGeofencing akzeptiert einen url. Das Plugin sendet per POST den Übergang vom nativen code, inklusive deiner gemergten payload, sodass dein Backend es erhält, selbst wenn JavaScript nie ausgeführt wird. Header werden im Setup persistiert, damit sie nach einem Relaunch noch funktionieren, und du kannst sie mit updateHeaders().
Delivery erfolgt nach dem Best-Effort-Prinzip ohne Retry-Queue. Halte den Endpoint schnell und idempotent und logge während des Testens jede Anfrage.
7. Ortungsdienste oder Hintergrundaktualisierung sind deaktiviert
Prüfe Folgendes auf dem Testgerät:
- Einstellungen > Datenschutz & Sicherheit > Ortungsdienste ist aktiviert.
- Deine App ist auf „Immer“ eingestellt.
- „Genauer Standort“ ist für deine App aktiviert. Ohne ihn sind kleine Regionen unzuverlässig.
- Der Stromsparmodus ist bei den ersten Tests aus. Er reduziert Hintergrundaktivitäten.
8. Neustarts und App-Updates
iOS behält überwachte Regionen über App-Starts hinweg. Nach einem Neustart des Geräts läuft die Überwachung weiter, sobald das System wieder da ist, aber deine App sollte ihre Regionen beim Start trotzdem neu abgleichen: Vergleiche getMonitoredGeofences() sie mit dem, was du erwartest, und füge fehlende wieder hinzu. Mach das Gleiche nach einem App-Update und immer, wenn der Server die Liste der Orte ändert.
Unter Android werden Geofences nach einem Reboot und in einigen anderen Situationen (Ortung aus, App-Daten gelöscht) entfernt, deshalb ist dort eine Neuregistrierung beim Start ohnehin nötig. Beides auf beiden Plattformen zu machen, hält die Logik einfach.
9. Erzwungenes Beenden
Wenn ein Nutzer die App im App-Switcher zwangsweise beendet, wertet iOS das als starkes Signal, dass der Nutzer die App nicht laufen lassen will. Wie die App nach einem erzwungenen Beenden per Region neu gestartet wird, hat sich über die iOS-Versionen hinweg geändert. Baue kein Feature, das nur funktioniert, wenn die App nach einem erzwungenen Beenden neu startet, und teste es auf den iOS-Versionen, die deine Nutzer verwenden.
Debugging-Checkliste
| Prüfen | So geht's |
|---|---|
| Berechtigung ist auf 'Immer' | checkPermissions() gibt zurück always oder granted für backgroundLocation |
| Region ist registriert | getMonitoredGeofences() listet die Identifier auf |
| Weniger als 20 Regionen | Zähle von getMonitoredGeofences() |
| Radius ≥ 100 m | Überprüfe deine addGeofence Aufrufe |
| Nicht bereits im Bereich | Aktuelle Position bei Registrierung vergleichen |
| Fehler werden protokolliert | Höre auf geofenceError |
| Backend empfängt Events | Setzen Sie url in setupGeofencing und logge Requests |
| Echter Bewegungstest | Region verlassen, warten, zurückkehren und bleiben |
Android-Unterschiede, die man beachten sollte
Wenn derselbe code auf Android funktioniert, aber nicht auf iOS, sind das 20-Regionen-Limit und die „Always“-Anforderung meist die Gründe. Wenn es auf iOS funktioniert, aber nicht auf Android, prüfe, ob du ACCESS_BACKGROUND_LOCATION in dein Manifest eingefügt und backgroundLocation: true in setupGeofencing. Das Plugin fügt diese Berechtigung nicht automatisch hinzu, da Google Play eine explizite Erklärung für den Standortzugriff im Hintergrund verlangt.
Änderungen ausliefern
Permission strings and native configuration require a new build. Region lists, radius values and UI text can be changed server side or shipped through Capgo Live Updates. Für kontinuierliches Tracking statt Enter/Exit-Events siehe warum iOS Hintergrund-Standort-Updates stoppt, und das Plugin Dokumentation für das vollständige API.