รับ Location ใน Background ต้องตั้งค่า Capabilities, Info.plist, และ CLLocationManager ให้ถูกต้อง
1. Capabilities (Xcode Project Settings)
- Signing & Capabilities → + Capability → Background Modes
- ติ๊ก Location updates
2. Info.plist Keys
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>แอปต้องการตำแหน่งที่ตั้งเพื่อฟีเจอร์ X แม้ขณะรันพื้นหลัง</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>แอปต้องการตำแหน่งที่ตั้งขณะใช้งานแอป</string>
<key>NSLocationAlwaysUsageDescription</key> <!-- iOS 10 และก่อน -->
<string>แอปต้องการตำแหน่งที่ตั้งตลอดเวลา</string>
3. โค้ด CLLocationManager
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
static let shared = LocationManager()
private let manager = CLLocationManager()
private var continuation: AsyncStream.Continuation?
private override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.allowsBackgroundLocationUpdates = true // สำคัญ!
manager.pausesLocationUpdatesAutomatically = false // ไม่หยุดอัตโนมัติ
manager.showsBackgroundLocationIndicator = true // แสดง Indicator สีน้ำเงิน
}
func requestPermission() {
manager.requestAlwaysAuthorization() // ขอ Always Permission
}
func startUpdating() {
manager.startUpdatingLocation()
manager.startMonitoringSignificantLocationChanges() // ประหยัดแบต
}
func stopUpdating() {
manager.stopUpdatingLocation()
manager.stopMonitoringSignificantLocationChanges()
}
// Delegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
continuation?.yield(location)
// ส่งไป Server ที่นี่
sendLocationToServer(location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location error: \(error)")
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways: print("Authorized Always")
case .authorizedWhenInUse: print("Authorized When In Use")
case .denied, .restricted: print("Denied")
case .notDetermined: print("Not Determined")
@unknown default: break
}
}
// Async Stream สำหรับ Modern Swift
var locationStream: AsyncStream {
AsyncStream { continuation in
self.continuation = continuation
}
}
private func sendLocationToServer(_ location: CLLocation) {
// Background Task สำหรับ Network
var taskID: UIBackgroundTaskIdentifier = .invalid
taskID = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(taskID)
}
// POST Location to API
// ...
UIApplication.shared.endBackgroundTask(taskID)
}
}
การใช้งาน
// ใน AppDelegate หรือ SceneDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) {
LocationManager.shared.requestPermission()
LocationManager.shared.startUpdating()
}
// ใช้ Async Stream
Task {
for await location in LocationManager.shared.locationStream {
print("Location: \(location.coordinate)")
}
}
Best Practices
- Significant Location Changes - ประหยัดแบตมากกว่า (Update เมื่อเคลื่อนที่ ~500m+)
- Background Task - ใช้
beginBackgroundTaskสำหรับ Network Call - Battery - ตั้ง
desiredAccuracyให้เหมาะสม (ไม่ใช่ kCLLocationAccuracyBest ตลอดเวลา) - Privacy - แจ้งผู้ใช้ชัดเจน ว่าทำไมต้องการ Location Always