iOS에서 지오펜스가 트리거되지 않는 원인은 거의 항상 몇 가지 중 하나입니다. 앱에 '항상(Always)' 위치 권한이 없거나, 지역을 추가할 때 기기가 이미 그 영역 안에 있었거나, 반경이 너무 작아 iOS가 경계 통과를 감지하지 못하거나, 앱이 20개 지역 제한에 도달했거나, 테스트에서 충분히 기다리지 않은 경우입니다. iOS 지오펜싱은 잘 동작하지만, 교차는 iOS 자체 스케줄에 따라 보고됩니다. 이 가이드에서는 지역 모니터링이 언제 이벤트를 발생시키는지, 그리고 Capacitor 앱을 디버깅하는 방법을 설명합니다 @capgo/background-geolocation, iOS와 Android의 네이티브 지오펜싱을 포함합니다.
iOS 영역 모니터링 작동 방식
iOS에서 지오펜스는 CLCircularRegion Core Location에 등록된 객체입니다. 등록되면 앱이 아니라 운영체제가 이들을 모니터링합니다. 앱이 일시 중단되거나 실행 중이 아니어도 iOS는 기지국, Wi-Fi, GPS를 조합해 배터리 소모를 낮추면서 해당 지역을 계속 추적합니다.
iOS가 기기가 경계를 넘었다고 판단하면, 필요하고 허용되는 경우 백그라운드에서 앱을 실행해 진입 또는 이탈 이벤트를 앱에 전달합니다. 이 판단을 좌우하는 몇 가지 규칙이 있습니다:
- iOS는 기기가 경계에 닿는 순간 교차를 보고하지 않습니다. Apple의 가이드라인에 따르면, 기기는 경계를 넘고 최소 거리만큼 더 이동한 뒤 이벤트가 전달되기까지 약 20초 이상 그 자리에 머물러야 합니다. 이는 GPS 지터로 인한 오작동을 막기 위한 것입니다.
- 지역 모니터링의 위치 정확도는 주변에 어떤 라디오가 있는지 여부에 따라 달라집니다. Wi-Fi가 밀집된 도시에서는 빠를 수 있지만, 시골 도로에서는 몇 분이나 걸릴 수 있습니다.
- 이벤트는 전환 시에만 발생합니다. 모니터링 시작 시 이미 해당 지역에 있는 것은 진입으로 간주되지 않습니다.
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). iOS에서는 '항상 허용'을 의미합니다.
이제, 실패 사례를 살펴보겠습니다.
1. '항상 허용' 권한이 없는 경우
iOS의 지역 모니터링은 '항상 허용' 권한이 필요합니다. '앱 사용 중에만 허용'으로 설정하면 앱이 백그라운드에 있거나 실행 중이 아닐 때는 이벤트가 전달되지 않는데, 바로 그게 지오펜스의 핵심입니다.
지역을 등록하기 전에 권한 상태를 확인하고, 자체 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가 '항상 허용'으로 업그레이드하는 프롬프트를 즉시 보여주지 않을 수 있습니다. '앱 사용 중에만 허용'을 선택한 사용자는 나중에 두 번째 프롬프트를 보게 될 수도 있습니다. '한 번만 허용'을 선택한 사용자는 권한이 일시적으로 부여되었다가 초기화됩니다. 지오펜싱이 핵심 기능이라면 앱을 시작할 때마다 상태를 확인하고, '항상 허용'이 아닐 경우 명확한 배너를 표시하세요.
2. 기기가 이미 해당 지역 안에 있는 경우
폰이 이미 지역 안에 있는 상태에서 지오펜스를 추가하면 iOS는 enter 이벤트를 보내지 않습니다. 경계를 넘지 않았기 때문입니다. 사무실 주변에 지역을 설정하고 책상에서 테스트할 때 개발팀이 흔히 겪는 문제입니다.
앱에서 해결 방법: 지역을 등록할 때 현재 위치와 해당 지역을 비교하여 '이미 안에 있는 경우'를 직접 처리하세요.
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;
enter 이벤트를 테스트하려면, 반경 밖으로 멀리 걸어가거나 운전해서 나간 뒤, 잠시 기다렸다가 다시 들어오세요.
3. 반경이 너무 작은 경우
iOS는 작은 반경도 허용하지만, 지역 모니터링은 Wi-Fi와 셀룰러 측위에 의존하므로 반경이 100m 아래로 내려가면 신뢰도가 급격히 떨어집니다. 상점 문 주변 30m 반경은 이벤트가 늦게 발생하거나, 고객이 떠난 뒤에 발생하거나, 아예 발생하지 않을 수 있습니다.
실용적인 값:
| 유스케이스 | 권장 반경 |
|---|---|
| 매장 또는 식당 도착 | 100~200m |
| 사무실, 캠퍼스, 창고 | 150~300m |
| 이웃 지역 또는 도시 구역 | 500m 이상 |
문 단위 정밀도가 필요하면, 사용자가 내부에 진입한 후 더 큰 지오펜스를 포그라운드 위치 정보나 비콘과 조합하세요. Capgo에도 iBeacon 플러그인 이 실내 근접성 감지에 있습니다.
4. 20개 지역 제한에 도달함
iOS 앱은 동시에 최대 20개 지역만 모니터링할 수 있습니다. 이보다 많이 추가하면 초과된 항목의 등록이 실패하고 모니터링 오류가 발생합니다.
사용 getMonitoredGeofences() 실제로 등록된 내용을 확인하고, 가장 가까운 지역들의 롤링 세트를 유지하세요:
const { regions } = await BackgroundGeolocation.getMonitoredGeofences();
console.log(`Monitoring ${regions.length} regions`, regions);
일반적인 패턴:
- 전체 장소 목록은 서버에 유지하세요.
- 앱이 시작되거나 사용자가 멀리 이동할 때(예: 사용자 주변의 큰 '새로고침' 영역을 벗어날 때), 가장 가까운 19개 장소를 가져오세요.
- 호출하세요
removeAllGeofences()그 다음, 가장 가까운 19개 장소와 사용자를 중심으로 한 큰 영역 하나를 추가하세요. 이 큰 영역을 벗어날 때를 새로고침 신호로 사용하세요.
Android는 앱당 최대 100개의 지오펜스를 허용하므로, Android에서 작동하던 code가 이 이유만으로 iOS에서 실패할 수 있습니다.
5. 테스트가 너무 짧거나 정적이었음
iOS는 기기가 명확하게 경계를 넘어 반대편에 머물렀을 때만 전환을 보고합니다. 50km/h로 150미터 영역을 통과하면 약 20초를 그 안에 머물게 됩니다. 이는 충분하지 않을 수 있습니다.
테스트할 때:
- 밖에서 1분간 머문 뒤 실내로 들어가 최소 2분은 머무르세요.
- Wi-Fi를 켠 상태로 테스트하세요. Wi-Fi를 끄면 어떤 네트워크에도 연결되어 있지 않더라도 지역 모니터링 정확도가 크게 떨어집니다.
- 타이밍은 Simulator에 의존하지 마세요. Xcode에서 GPX 경로로 핸들러를 확인한 다음 기기에서 테스트하세요.
6. 이벤트는 발생했지만 JavaScript가 실행되지 않는 경우
지역 이벤트로 인해 iOS가 백그라운드에서 앱을 재실행할 때, 네이티브 플러그인은 이벤트를 수신하지만 Capacitor WebView는 로드되지 않았거나 곧바로 다시 일시 중단될 수 있습니다. 해당 geofenceTransition 리스너는 WebView가 살아 있을 때만 동작합니다.
이러한 이유로 setupGeofencing …을 받습니다 url. 플러그인은 네이티브 code에서 전환을 POST하며, 여기에는 병합된 payload헤더가 포함되므로 JavaScript가 전혀 실행되지 않아도 백엔드에서도 받을 수 있습니다. 헤더는 설정과 함께 저장되어 재실행 후에도 계속 동작하며, ...로 교체할 수 있습니다. updateHeaders().
전송은 재시도 큐가 없는 best effort 방식입니다. 엔드포인트를 빠르고 멱등하게 만들고, 테스트 중에는 모든 요청을 로그로 남기세요.
7. 위치 서비스 또는 백그라운드 앱 새로 고침이 꺼져 있음
테스트 기기에서 다음 설정을 확인하세요:
- 설정 > 개인정보 보호 및 보안 > 위치 서비스가 켜져 있어야 합니다.
- 앱 권한이 '항상'으로 설정되어 있어야 합니다.
- 앱의 '정확한 위치'가 켜져 있어야 합니다. 꺼져 있으면 좁은 영역을 감지하기 어렵습니다.
- 첫 테스트 시 '저전력 모드'를 꺼두세요. 저전력 모드는 백그라운드 활동을 제한합니다.
8. 재부팅 및 앱 업데이트
iOS는 앱을 다시 실행해도 모니터링 중인 지역을 유지합니다. 기기를 재시작하면 시스템이 복구된 후 모니터링이 재개되지만, 앱 실행 시 지역을 다시 동기화해야 합니다. 비교하세요: getMonitoredGeofences() 기대하는 목록과 비교해 누락된 지역을 다시 추가하세요. 앱 업데이트 후나 서버의 장소 목록이 변경될 때도 동일하게 하세요.
Android의 경우 재부팅이나 기타 상황(위치 서비스 해제, 앱 데이터 삭제 등)에서 geofence가 삭제되므로, 어차피 앱 실행 시 재등록이 필요합니다. 양쪽 플랫폼 모두에서 이 로직을 적용하면 구현이 단순해집니다.
9. 앱 강제 종료
사용자가 앱 스위처에서 앱을 강제 종료하면, iOS는 사용자가 앱 실행을 원치 않는다는 강력한 신호로 간주합니다. 강제 종료 후 지역 재실행 동작은 iOS 버전에 따라 달라졌습니다. 강제 종료 후 앱이 재실행되어야만 작동하는 기능을 만들지 마세요. 또한 사용자가 사용하는 iOS 버전에서 테스트하세요.
디버깅 체크리스트
| 체크 항목 | 방법 |
|---|---|
| 항상 권한 | checkPermissions() 반환합니다 always 또는 granted 위해 backgroundLocation |
| 지역이 등록됨 | getMonitoredGeofences() 식별자를 나열합니다 |
| 20개 지역 미만 | 다음에서 카운트 getMonitoredGeofences() |
| 반경 ≥ 100m | 다음 항목 검토 addGeofence 호출합니다 |
| 이미 내부에 있지 않음 | 등록 시 현재 위치 비교 |
| 에러가 기록됨 | 청취하기 geofenceError |
| 백엔드가 이벤트를 수신합니다 | 설정 url 에서 setupGeofencing 요청을 로그로 기록합니다 |
| 실제 이동 테스트 | 영역에서 완전히 벗어나 기다렸다가 다시 돌아와 머무릅니다 |
염두에 둘 Android 차이점
같은 code가 Android에서는 작동하지만 iOS에서는 작동하지 않는다면, 20개 지역 제한과 Always 요구 사항이 일반적인 원인입니다. iOS에서는 작동하지만 Android에서는 작동하지 않는다면, manifest에 ACCESS_BACKGROUND_LOCATION 을(를) 추가하고 backgroundLocation: true 에서 setupGeofencing을(를) 설정했는지 확인하세요. Google Play는 백그라운드 위치 정보에 대한 선언을 요구하기 때문에 플러그인이 해당 권한을 자동으로 추가하지 않습니다.
변경 사항 배포
권한 문자열과 네이티브 설정은 새로운 빌드가 필요합니다. 지역 목록, 반경 값, UI 텍스트는 서버 측에서 변경하거나 Live Update를 통해 배포할 수 있습니다 Capgo Live Update 사용하기연속 추적은 진입 및 이탈 이벤트가 아닌 경우, iOS가 백그라운드 위치 업데이트를 중단하는 이유, 그리고 플러그인 문서 API 전체 내용을 보려면