📍 Core Location 완전정복
GPS 위치 추적부터 지오펜싱까지. 위치 기반 서비스의 모든 것!
⭐ 난이도: ⭐⭐
⏱️ 예상 시간: 1-2h
📂 App Services
✨ Core Location이란?
Core Location은 GPS, Wi-Fi, 셀룰러를 활용해 사용자 위치를 제공합니다. 위치 추적, 지오펜싱, 나침반 등을 구현할 수 있습니다.
🔐 권한 요청
Info.plist
<!-- 필수: 사용 목적 설명 --> <!-- 앱 사용 중에만 위치 --> <key>NSLocationWhenInUseUsageDescription</key> <string>현재 위치를 지도에 표시하기 위해 필요합니다</string> <!-- 항상 위치 (백그라운드) --> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>지오펜싱 알림을 위해 백그라운드 위치 접근이 필요합니다</string>
📍 현재 위치 가져오기
LocationManager.swift
import CoreLocation class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject { private let manager = CLLocationManager() @Published var location: CLLocation? @Published var authorizationStatus: CLAuthorizationStatus? override init() { super.init() manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyBest } // 권한 요청 func requestPermission() { manager.requestWhenInUseAuthorization() // 또는 manager.requestAlwaysAuthorization() } // 위치 추적 시작 func startUpdating() { manager.startUpdatingLocation() } // 위치 추적 중지 func stopUpdating() { manager.stopUpdatingLocation() } // 한 번만 위치 가져오기 (iOS 14+) func requestLocation() { manager.requestLocation() } // Delegate - 위치 업데이트 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { location = locations.last print("위치: \(location!.coordinate.latitude), \(location!.coordinate.longitude)") } // Delegate - 권한 변경 func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { authorizationStatus = manager.authorizationStatus switch manager.authorizationStatus { case .authorizedWhenInUse, .authorizedAlways: startUpdating() case .denied, .restricted: print("위치 권한 거부") case .notDetermined: requestPermission() @unknown default: break } } // Delegate - 에러 func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("위치 오류: \(error.localizedDescription)") } }
📱 SwiftUI 통합
LocationView.swift
import SwiftUI struct LocationView: View { @StateObject private var locationManager = LocationManager() var body: some View { VStack(spacing: 20) { if let location = locationManager.location { Text("📍 현재 위치") .font(.headline) Text("위도: \(location.coordinate.latitude)") Text("경도: \(location.coordinate.longitude)") Text("고도: \(location.altitude)m") Text("정확도: \(location.horizontalAccuracy)m") } else { Text("위치 정보 없음") Button("위치 권한 요청") { locationManager.requestPermission() } .buttonStyle(.borderedProminent) } } .padding() } }
🎯 지오펜싱 (Geofencing)
Geofencing.swift
import CoreLocation class GeofenceManager: NSObject, CLLocationManagerDelegate { private let manager = CLLocationManager() override init() { super.init() manager.delegate = self } // 지오펜스 추가 (특정 지역 모니터링) func startMonitoring( identifier: String, center: CLLocationCoordinate2D, radius: CLLocationDistance ) { // 권한 체크 guard manager.authorizationStatus == .authorizedAlways else { manager.requestAlwaysAuthorization() return } // 원형 지역 생성 let region = CLCircularRegion( center: center, radius: radius, // 미터 단위 (최대 400m) identifier: identifier ) region.notifyOnEntry = true // 진입 시 알림 region.notifyOnExit = true // 퇴출 시 알림 manager.startMonitoring(for: region) } // 지오펜스 제거 func stopMonitoring(identifier: String) { for region in manager.monitoredRegions { if region.identifier == identifier { manager.stopMonitoring(for: region) } } } // Delegate - 지역 진입 func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { print("✅ \(region.identifier) 지역에 진입했습니다") // 로컬 알림 표시 sendNotification(title: "지역 진입", body: "\(region.identifier)에 도착했습니다") } // Delegate - 지역 퇴출 func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { print("🚪 \(region.identifier) 지역을 벗어났습니다") sendNotification(title: "지역 퇴출", body: "\(region.identifier)을 떠났습니다") } func sendNotification(title: String, body: String) { // UNUserNotificationCenter 사용 } } // 사용 예시 let geofence = GeofenceManager() geofence.startMonitoring( identifier: "집", center: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780), radius: 100 // 100미터 )
🧭 나침반 (Heading)
Compass.swift
import CoreLocation class CompassManager: NSObject, CLLocationManagerDelegate, ObservableObject { private let manager = CLLocationManager() @Published var heading: Double = 0 // 0-360도 override init() { super.init() manager.delegate = self manager.headingFilter = 5 // 5도 이상 변경 시만 업데이트 } func startUpdatingHeading() { if CLLocationManager.headingAvailable() { manager.startUpdatingHeading() } } func stopUpdatingHeading() { manager.stopUpdatingHeading() } // Delegate - 방향 업데이트 func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { heading = newHeading.trueHeading // 진북 기준 // newHeading.magneticHeading - 자북 기준 print("방향: \(heading)° \(getDirection(heading))") } func getDirection(_ heading: Double) -> String { switch heading { case 0..<22.5: return "북" case 22.5..<67.5: return "북동" case 67.5..<112.5: return "동" case 112.5..<157.5: return "남동" case 157.5..<202.5: return "남" case 202.5..<247.5: return "남서" case 247.5..<292.5: return "서" case 292.5..<337.5: return "북서" default: return "북" } } }
📏 거리 계산
Distance.swift
import CoreLocation // 두 좌표 사이 거리 (미터) func calculateDistance(from: CLLocation, to: CLLocation) -> CLLocationDistance { return from.distance(from: to) } // 사용 예시 let seoul = CLLocation(latitude: 37.5665, longitude: 126.9780) let busan = CLLocation(latitude: 35.1796, longitude: 129.0756) let distance = calculateDistance(from: seoul, to: busan) print("거리: \(distance / 1000)km") // 약 325km
⚙️ 정확도 설정
Accuracy.swift
let manager = CLLocationManager() // 정확도 설정 (배터리 소모 ↔️ 정확도 트레이드오프) manager.desiredAccuracy = kCLLocationAccuracyBest // 최고 정확도 manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters // 10m manager.desiredAccuracy = kCLLocationAccuracyHundredMeters // 100m manager.desiredAccuracy = kCLLocationAccuracyKilometer // 1km manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers // 3km // 최소 이동 거리 설정 (이보다 적게 이동하면 업데이트 안 함) manager.distanceFilter = 10 // 10미터 // 배터리 절약 모드 (iOS 9+) manager.allowsBackgroundLocationUpdates = false // 백그라운드 업데이트 끄기
🔋 백그라운드 위치 추적
BackgroundLocation.swift
// 1. Info.plist에 추가 // NSLocationAlwaysAndWhenInUseUsageDescription // 2. Capabilities 설정 // Xcode > Signing & Capabilities > Background Modes // ✅ Location updates 체크 // 3. 코드 설정 let manager = CLLocationManager() manager.allowsBackgroundLocationUpdates = true manager.pausesLocationUpdatesAutomatically = false // Always 권한 요청 manager.requestAlwaysAuthorization() // ⚠️ 주의: 백그라운드 위치는 배터리를 많이 소모합니다! // 꼭 필요한 경우에만 사용하세요.
📊 위치 정확도 확인
AccuracyCheck.swift
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let location = locations.last else { return } // 수평 정확도 (작을수록 정확) print("수평 정확도: \(location.horizontalAccuracy)m") // 수직 정확도 (고도) print("수직 정확도: \(location.verticalAccuracy)m") // 정확도가 낮으면 무시 if location.horizontalAccuracy > 100 { print("정확도 너무 낮음, 무시") return } // 오래된 데이터 무시 (5초 이상) if Date().timeIntervalSince(location.timestamp) > 5 { print("오래된 위치 데이터, 무시") return } // 유효한 위치 데이터 사용 print("✅ 정확한 위치: \(location.coordinate)") }
⚠️ 배터리 주의!
- 위치 추적은 배터리를 많이 소모합니다
- 적절한 정확도와 distanceFilter 설정
- 불필요할 때는 stopUpdatingLocation() 호출
- 백그라운드 위치는 꼭 필요할 때만
💡 HIG 체크리스트
✅ 위치 사용 목적 명확히 설명
✅ 적절한 권한만 요청 (WhenInUse vs Always)
✅ 권한 거부 시 앱 정상 작동
✅ 배터리 소모 최소화
✅ 정확도 트레이드오프 고려