Zum Hauptinhalt springen

Warum Geofences unter iOS nicht ausgelöst werden

Geofences triggern auf iOS in deiner Capacitor-App nicht? Erfahre, wie Region Monitoring funktioniert, welche Regeln für Berechtigung, Radius und Limits gelten und wie du jeden Fall debuggst.

Warum Geofences unter iOS nicht ausgelöst werden

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:

  1. Speichere deine vollständige Liste an Orten auf dem Server.
  2. 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.
  3. 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.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.