Core Location Cookbook

1. Giới thiệu:

Core Location framework trong iOS cung cấp các service liên quan tới vị trí của thiết bị, độ cao, hướng... Core Location sử dụng toàn bộ các phần cứng của iPhone nếu có thể để bổ trợ thêm cho việc xác định vị trí thêm chính xác như Wi-Fi, GPS, Bluetooth, cảm biến nam châm, khí áp kế, ăng ten bắt sóng di động.

Tùy thuộc vào mục đích sử dụng, chúng ta có thể sử dụng một số dịch vụ được cung cấp sẵn bởi Core Location.

  • Dịch vụ cơ bản (Standard location service): cung cấp nhiều tùy chọn để lấy vị trí hiện tại , theo dõi các thay đổi.
  • Dịch vụ theo dõi theo vùng (Region monitoring service): cho phép chúng ta theo dõi 1 vùng địa lý hay vùng iBeacon, hệ thống sẽ phát ra các thông báo khi chúng ta đi vào hay đi ra khỏi ranh giới các vùng này.
  • Dịch vụ theo dõi sự thay đổi lớn của vị trí (significant-change location service): cho phép chúng ta lấy vị trí hiện tại cũng như thông báo mỗi khi có sự thay đổi lớn về vị trí, ví dụ như 500m hay lớn hơn.

2. Một số bài toán thực tế:

2.1. Lấy vị trí hiện tại:

Để lấy vị trí hiện tại chúng ta có thể khai báo 1 instance của CLLocationManager, sau đó gọi hàm requestLocation

manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
manager.delegate = self
manager.requestLocation()

Giá trị location sẽ được trả về thông qua hàm delegate locationManager:didUpdateLocations: của CLLocationManager.

public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.max(by: { (location1, location2) -> Bool in
        return location1.timestamp.timeIntervalSince1970 < location2.timestamp.timeIntervalSince1970}) else { return }
    
    // do something with the location
}

Khi gọi requestLocation, service sẽ cố gắng lấy vị trí hiện tại căn cứ theo độ chính xác mà chúng ta thiết lập ở desiredAccuracy, giá trị trả về ở didUpdateLocations là 1 mảng giá trị và chúng ta cần lấy giá trị có thời gian gần với thời gian hiện tại nhất.

Các giá trị có thể thiết lập cho desiredAccuracy:

  • kCLLocationAccuracyBestForNavigation: sử dụng GPS và các cảm biến khác để lấy vị trí chính xác nhất có thể, thiết lập này khuyến cáo là chỉ nên sử dụng khi mà thiết bị được cắm vào dây nguồn. Thích hợp với các ứng dụng chỉ đường theo thời gian thực.
  • kCLLocationAccuracyBest: sử dụng GPS, có độ chính xác tương đối giống kCLLocationAccuracyBestForNavigation
  • kCLLocationAccuracyNearestTenMeters: sử dụng GPS, cho độ chính xác có sai số trong phạm vi 10m
  • kCLLocationAccuracyHundredMeters: lấy vị trí qua wifi, khi ở khu vực trống sẽ lấy qua GPS, độ chính xác trong khoảng 24 ~ 100m. Thích hợp với các app hay sử dụng trong nhà (indoor)
  • kCLLocationAccuracyKilometer: lấy vị trí thông qua các trạm phát sóng di động, độ chính xác ~ 1000m
  • kCLLocationAccuracyThreeKilometers: lấy vị trí thông qua các trạm phát sóng di động, độ chính xác ~ 3000m. Thích hợp với các app chỉ cần lấy vị trí để xác định thành phố như các app về thời tiết...

Tùy thuộc vào mục đích sử dụng mà chúng ta chọn độ chính xác cho phù hợp, việc lựa chọn độ chính xác cao không cần thiết sẽ làm cho thiết bị phải thực hiện nhiều request để cố gắng lấy được vị trí có độ chính xác như mong muốn, và xử lý tính toán gây tốn pin.

2.2. Lấy vị trí liên tục theo thời gian thực:

Cũng giống như trên nhưng chúng ta gọi hàm startUpdatingLocation:

manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
manager.distanceFilter = kCLDistanceFilterNone
manager.delegate = self
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()

Vị trí vẫn sẽ được gửi về qua locationManager:didUpdateLocations:

Trong trường hợp này ta cần thiết lập thêm giá trị cho thuộc tính distanceFilter, căn cứ vào thuộc tính này, hệ thống sẽ gửi vị trí về mỗi khi client đi được quãng đường như đã thiết lập. Mặc định giá trị là kCLDistanceFilterNone, tức là vị trí sẽ gửi về sau mỗi khoảng thời gian là 1s mà không quan tâm tới quãng đường.

Nếu ta thiết lập distanceFilter = 10 chẳng hạn, hệ thống sẽ gửi vị trí về sau khi client đi được mỗi 10m.

Có 1 điều lưu ý là thiết lập distanceFilter chỉ giới hạn tần suất hệ thống gửi vị trí về client qua delegate, việc lấy vị trí vẫn thực hiện liên tục và việc tốn pin trong trường hợp này là như nhau.

2.3. Lấy vị trí liên tục dưới background:

Để lấy vị trí liên tục ngay cả khi app bị thu dưới background. Chúng ta cần cấu hình thêm key UIBackgroundModesNSLocationAlwaysUsageDescription ở file info.plist.

<key>NSLocationAlwaysUsageDescription</key>
<string>Record routes</string>
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>location</string>
</array>

Mặc định CLLocationManager sẽ để thuộc tính pausesLocationUpdatesAutomatically = true với mục đích cho phép hệ thống tạm ngưng lấy location khi hệ thống phân tích và cho rằng người dùng ít có khả năng thay đổi vị trí. Ví dụ khi người dùng dừng lại đi shopping trong khi đang sử dụng ứng dụng chỉ đường, hệ thống sẽ dừng việc cập nhật vị trí. Trong trường hợp này chúng ta nên thiết lập thuộc tính activityType của CLLocationManager để giúp hệ thống xác định thời điểm tạm dừng được chính xác hơn.

Cần lưu ý là mỗi khi hệ thống tạm dừng cập nhật, nó sẽ gọi locationManagerDidPauseLocationUpdates(_:) của delegate, nhiệm vụ của chúng ta là cấu hình một local notification với trigger có kiểu là UNLocationNotificationTrigger để hiện thông báo người dùng bật app trở lại để tiếp tục update.

Trong trường hợp bạn không muốn hệ thống tự động tạm dừng, chúng ta có thể thiết lập pausesLocationUpdatesAutomatically = false

manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
manager.distanceFilter = kCLDistanceFilterNone
manager.delegate = self
manager.pausesLocationUpdatesAutomatically = false
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()

2.4. Theo dõi sự thay đổi lớn của vị trí:

Trong trường hợp ứng dụng của chúng ta cần theo dõi sự thay đổi lớn về vị trí của người dùng, ví dụ như di chuyển từ thành phố này sang thành phố khác để cập nhật tình hình thời tiết, ta có thể dùng hàm startMonitoringSignificantLocationChanges. Việc thiết lập như sau:

manager = CLLocationManager()
manager.allowsBackgroundLocationUpdates = true
manager.delegate = self
manager.requestAlwaysAuthorization()
manager.startMonitoringSignificantLocationChanges()

Lưu ý là hệ thống sẽ bỏ qua thiết lập desiredAccuracydistanceFilter khi theo dõi.

Chúng ta cũng cần thiết lập trong info.plist như mục 2.3.

Sau khi khởi động service và app của chúng ta bị tắt, hệ thống sẽ tự động bật lại app dưới background khi nhận được sự kiện thay đổi vị trí. Trong trường hợp này, hệ thống sẽ gửi kèm 1 key trong dictionary của hàm application(_:willFinishLaunchingWithOptions:)application(_:didFinishLaunchingWithOptions:), chúng ta cần cấu hình lại location manager và gọi lại hàm startMonitoringSignificantLocationChanges

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        guard let launchOptions = launchOptions  else {
            return
        }
        
        if launchOptions[UIApplicationLaunchOptionsKey.location] != nil {
            // start startMonitoringSignificantLocationChanges
        }
}

Notification sẽ chỉ được gửi về khi thiết bị di chuyển cách 500m so với notification trước, và 2 lần gửi sẽ phải cách nhau ít nhất 5p. Khi thiết bị có kết nối mạng thì việc gửi sẽ đảm bảo đúng lúc, đúng khoảng cách hơn là khi không có mạng.

2.5. Theo dõi vùng địa lý:

Giả sử chúng ta có bài toán là cần theo dõi 1 vùng có dạng hình tròn tâm là currentCoordinate, bán kính 100m, có thông báo mỗi khi người dùng ra và vào vùng, chúng ta có thể làm như sau:

manager = CLLocationManager()
manager.allowsBackgroundLocationUpdates = true
manager.delegate = self
manager.requestAlwaysAuthorization()

let region = CLCircularRegion(center: currentCoordinate, radius: 100, identifier: "region1")
manager.startMonitoring(for: region)

Khi hệ thống xác định được việc vào/ra vùng theo dõi sẽ gửi về thông qua các hàm delegate:

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {

}

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {

}

Chú ý là việc gửi thông báo có thể bị trễ 3-5p so với thời điểm thực tế của việc ra/vào vùng hoặc có thể lâu hơn nếu thiết bị không có kết nối mạng.

2.6. Theo dõi các địa điểm của người dùng:

Core Location có một tính năng khá hay đó là cho phép ghi lại các vị trí mà người dùng đi tới, bao gồm tọa độ, thời điểm đến và đi của người dùng.

manager = CLLocationManager()
manager.allowsBackgroundLocationUpdates = true
manager.delegate = self
manager.startMonitoringVisits()

Kết quả cũng được trả về thông qua delegate:

public func locationManager(_ manager: CLLocationManager, didVisit visit: CLVisit) {

}

Khi app bị tắt trong khi chúng ta đang theo dõi địa điểm, hệ thống sẽ tự động bật lại app. Để nhận sự kiện chúng ta cần tạo lại location manager và gán delegate ở hàm application(_:willFinishLaunchingWithOptions:)application(_:didFinishLaunchingWithOptions:).

Chú ý là cũng như các dịch vụ theo dõi khác, dịch vụ theo dõi địa điểm cũng bị trễ 3-5p và có thể nhiều hơn nếu thiết bị không kết nối mạng.

3. Kết luận:

Trên đây là một số bài toán thực tế sử dụng Core Location mà chúng ta có thể gặp. Hi vọng sẽ giúp ích được cho các bạn.