Aller au contenu principal

Pourquoi les geofences ne se déclenchent pas sur iOS

Vos géofences ne se déclenchent pas sur iOS dans votre application Capacitor ? Découvrez comment fonctionne le monitoring de région, les règles d'autorisation, de rayon et de limite, et comment déboguer chaque cas.

Carte avec des limites de localisation illustrant le géorepérage dans une application iOS Capacitor

Lorsque les géofences ne se déclenchent pas sur iOS, la cause provient presque toujours de l'un des points suivants : l'application n'a pas l'autorisation « Toujours », l'appareil se trouvait déjà dans la région lors de son ajout, le rayon est trop petit pour qu'iOS détecte un passage, l'application a atteint la limite de 20 régions, ou le test n'a pas attendu assez longtemps. Le geofencing sur iOS fonctionne bien, mais il signale les passages à son propre rythme. Ce guide explique comment le monitoring de région décide quand se déclencher et comment déboguer une application Capacitor en utilisant @capgo/background-geolocation, ce qui inclut le geofencing natif sur iOS et Android.

Fonctionnement de la surveillance de région sur iOS

Sur iOS, les geofences sont des CLCircularRegion objets enregistrés auprès de Core Location. Une fois enregistrés, c'est le système d'exploitation qui les surveille, pas votre application. Votre application peut être suspendue ou ne pas être en cours d'exécution et iOS continuera de suivre les régions, en utilisant un mélange d'antennes relais, de Wi-Fi et de GPS pour limiter la consommation de la batterie.

Lorsque iOS détermine que l'appareil a franchi une limite, il transmet un événement d'entrée (enter) ou de sortie (exit) à l'application, en la lançant en arrière-plan si nécessaire et si les permissions le permettent. Quelques règles régissent cette décision :

  • iOS ne signale pas un franchissement dès que l'appareil touche la limite. Les directives d'Apple précisent que l'appareil doit traverser la limite, parcourir une distance minimale au-delà de celle-ci et y rester au moins une vingtaine de secondes avant que l'événement ne soit transmis. Cela évite les déclenchements dus au jitter GPS.
  • La précision de la localisation pour la surveillance de région dépend des technologies radio à proximité. Dans une ville au Wi-Fi dense, elle peut être rapide. Sur une route de campagne, elle peut prendre plusieurs minutes.
  • Les événements ne concernent que les transitions. Être déjà dans une région au démarrage de la surveillance ne compte pas comme une entrée.

Configurer le geofencing dans une app Capacitor

Installez le plugin et configurez les clés 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>

Enregistrez ensuite les régions :

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 demande la permission nécessaire au geofencing par défaut (requestPermissions par défaut sur true). Sur iOS, cela signifie Toujours.

Passons maintenant aux modes d’échec.

1. Vous n’avez pas l’autorisation « Toujours »

La surveillance de régions sur iOS nécessite l’autorisation « Toujours ». Avec « Pendant l’utilisation de l’application » uniquement, vos régions ne déclencheront pas d’événements lorsque l’application est en arrière-plan ou fermée, ce qui est précisément l’objectif d’un géorepérage.

Vérifiez avant d’enregistrer des régions, et expliquez d’abord la demande dans votre propre interface :

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();
  }
}

Notez qu’iOS peut ne pas afficher immédiatement le prompt de passage à « Toujours ». Les utilisateurs qui ont choisi « Autoriser pendant l’utilisation de l’app » peuvent ne voir la seconde demande que plus tard. Ceux qui ont choisi « Autoriser une fois » obtiennent une autorisation temporaire qui se réinitialise. Si le géorepérage est une fonctionnalité clé, vérifiez le statut à chaque lancement de l’app et affichez un bandeau clair lorsqu’il n’est pas sur « Toujours ».

2. L’appareil était déjà à l’intérieur de la zone

Si vous ajoutez une zone de géorepérage alors que le téléphone s’y trouve déjà, iOS n’envoie pas d’événement d’entrée. Aucun franchissement n’a eu lieu. Les équipes rencontrent constamment ce problème quand elles testent à leur bureau avec une zone centrée sur celui-ci.

Corrigez-le dans votre app : quand vous enregistrez une zone, comparez la position actuelle à la zone et gérez vous-même le cas « déjà à l’intérieur ».

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;

Pour tester un événement d’entrée, éloignez-vous à pied ou en voiture bien au-delà du rayon, attendez, puis revenez.

3. Le rayon est trop petit

iOS accepte de petits rayons, mais la fiabilité chute rapidement en dessous d’environ 100 mètres, car le suivi de zone s’appuie sur le Wi-Fi et la géolocalisation cellulaire. Une zone de 30 mètres autour de la porte d’un store peut se déclencher en retard, après le départ du client, ou ne pas se déclencher du tout.

Valeurs pratiques :

Cas d'usage Rayon suggéré
Arrivée au store ou au restaurant 100 à 200 m
Bureau, campus, entrepôt 150 à 300 m
Quartier ou zone urbaine 500 m et plus

Si vous avez besoin d’une précision au niveau de la porte, combinez une geofence plus large avec la localisation en premier plan ou un beacon une fois l’utilisateur à l’intérieur. Capgo propose également un plugin iBeacon pour la proximité en intérieur.

4. Vous atteignez la limite de 20 régions

Une application iOS peut surveiller au maximum 20 régions à la fois. Si vous en ajoutez davantage, l’enregistrement échoue pour les régions supplémentaires et vous recevez une erreur de surveillance.

Utiliser getMonitoredGeofences() pour voir ce qui est réellement enregistré, et maintenez un ensemble tournant des régions les plus proches :

const { regions } = await BackgroundGeolocation.getMonitoredGeofences();
console.log(`Monitoring ${regions.length} regions`, regions);

Un schéma courant :

  1. Gardez votre liste complète d’endroits sur le serveur.
  2. Au démarrage de l’app, et lorsque l’utilisateur se déplace loin (par exemple en sortant d’une grande région de « rafraîchissement » autour de lui), récupérez les 19 endroits les plus proches.
  3. Appelez removeAllGeofences(), puis ajoutez les 19 plus proches ainsi qu’une grande région centrée sur l’utilisateur. La sortie de cette grande région est votre signal pour rafraîchir.

Android autorise jusqu’à 100 geofences par app, donc code qui fonctionne sur Android peut échouer sur iOS pour cette seule raison.

5. Le test était trop court ou trop statique

iOS ne signale les transitions qu’après que l’appareil a clairement franchi la frontière et est resté de l’autre côté. Si vous traversez une région de 150 mètres à 50 km/h, vous y passez environ 20 secondes. Cela peut ne pas suffire.

Lors des tests :

  • Restez à l’extérieur une minute, puis entrez et restez au moins quelques minutes.
  • Testez avec le Wi-Fi activé. Désactiver le Wi-Fi rend la surveillance des régions beaucoup moins précise, même si vous n’êtes connecté à aucun réseau.
  • Ne vous fiez pas au Simulateur pour le timing. Utilisez-l’avec une route GPX dans Xcode pour vérifier vos handlers, puis testez sur un appareil.

6. L’événement s’est déclenché, mais votre JavaScript n’a pas été exécuté

Lorsque iOS relance votre application en arrière-plan pour un événement de région, le plugin natif le reçoit, mais le WebView Capacitor peut ne pas être chargé ou peut être suspendu à nouveau rapidement. Le geofenceTransition listener ne se déclenche que tant que le WebView est actif.

C’est pourquoi setupGeofencing accepte un url. Le plugin envoie la transition depuis le natif code, y compris vos payload, afin que votre backend la reçoive même si JavaScript ne s’exécute jamais. Les en-têtes sont persistés avec la configuration pour continuer à fonctionner après un redémarrage, et vous pouvez les faire pivoter avec updateHeaders().

La livraison est effectuée au mieux, sans file de réessai. Renseignez un endpoint rapide et idempotent, et journalisez chaque requête pendant les tests.

7. Services de localisation ou Actualisation en arrière-plan désactivés

Vérifiez ces éléments sur l’appareil de test :

  • Réglages, Confidentialité et sécurité, Services de localisation est activé.
  • Votre application est configurée sur Toujours.
  • La localisation précise est activée pour votre application. Sans elle, les petites régions sont peu fiables.
  • Le mode Économie d’énergie est désactivé lors de vos premiers tests. Il réduit l’activité en arrière-plan.

8. Redémarrages et mises à jour de l’app

iOS conserve les régions surveillées d’un lancement de l’app à l’autre. Après un redémarrage de l’appareil, la surveillance reprend une fois le système de nouveau opérationnel, mais votre app doit quand même resynchroniser ses régions au lancement : comparez getMonitoredGeofences() ce que vous attendez et réajoutez les éléments manquants. Faites de même après une mise à jour de l’application, et chaque fois que le serveur modifie la liste des lieux.

Sur Android, les geofences sont supprimées après un redémarrage et dans quelques autres cas (localisation désactivée, données de l’app effacées), il faut donc de toute façon les réenregistrer au lancement. Le faire sur les deux plateformes simplifie la logique.

9. Fermeture forcée

Si un utilisateur force la fermeture de l’app depuis le sélecteur d’applications, iOS l’interprète comme un signal fort que l’utilisateur ne veut pas que l’app tourne. Le comportement de relance des régions après une fermeture forcée a évolué selon les versions d’iOS. Ne concevez pas de fonctionnalité qui ne marche que si l’app est relancée après une fermeture forcée, et testez-la sur les versions d’iOS que vos utilisateurs utilisent.

Checklist de débogage

Vérifier Comment
Autorisation « Toujours » checkPermissions() renvoie always ou granted pour backgroundLocation
La région est enregistrée getMonitoredGeofences() liste l’identifiant
Moins de 20 régions Compter depuis getMonitoredGeofences()
Rayon ≥ 100 m Vérifiez votre addGeofence appelle
Pas encore à l'intérieur Comparer la position actuelle lors de l'enregistrement
Les erreurs sont enregistrées dans les logs Écouter geofenceError
Le backend reçoit les événements Définissez url dans setupGeofencing et enregistre les requêtes
Test de déplacement réel Quittez complètement la zone, attendez, revenez et restez

Différences Android à garder en tête

Si le même code fonctionne sur Android mais pas sur iOS, la limite de 20 régions et l'exigence du mode « Always » sont généralement en cause. S'il fonctionne sur iOS mais pas sur Android, vérifiez que vous avez bien ajouté ACCESS_BACKGROUND_LOCATION à votre manifest et configuré backgroundLocation: true dans setupGeofencing. Le plugin n'ajoute pas cette permission à votre place, car Google Play exige une déclaration pour la localisation en arrière-plan.

Déployer des changements

Les chaînes de permission et la configuration native nécessitent un nouveau build. Les listes de régions, les valeurs de rayon et le texte de l'UI peuvent être modifiés côté serveur ou déployés via les live updates Capgo. Pour un suivi continu plutôt que des événements d'entrée et de sortie, consultez pourquoi iOS arrête la localisation en arrière-plan, et le plugin documentation pour l’API complet.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.