Đóng gói CLLocationManager

Lấy thông tin vị trí hiện tại của ứng dụng (device) từ lâu đã trở thành 1 trong những yêu cầu quá thường xuyên đối với khách hàng.Và do đó, các lập trình viên cũng đã quá quen thuộc và xử lý task này không mấy khó khăn. Mình cũng đã code rất nhiều dự án với nhưng yêu cầu khác nhau, ví dụ như: lấy thông tin location khi có sự thay đổi, lấy current location ngay lập tức, monitor region (beacon, geofence).... Bài này mình sẽ trình bày 1 phương pháp mà mình thường xuyên xử dụng để thực hiện với task này.

Tạo class LocationManager

  • Bản chất việc lấy location của thiết bị nó ko bị ảnh hưởng hay phụ thuộc vào các viewcontroller. Nó có thể chạy 1 cách độc lập, song song với các với cách tác vụ khác.Do đó, cách tốt nhất để xử lý và lấy thông tin cần thiết là đóng gọi nó vào 1 class để quản lý.Ngoài ra, để không phải init CLLocationManager nhiều lần, hay muốn lưu thông tin location gần nhất thì mình thường quản lý bằng 1 singleton class.
class LocationManager: NSObject {

    // MARK: - Varialbes
    fileprivate var locationManager: CLLocationManager?
    
    // MARK: - Singleton
    static let shared = LocationManager()

    // MARK: - Init
    override init() {
        super.init()
    }

    func startLocationServices() {
        self.locationManager = CLLocationManager()
        self.locationManager?.delegate = self
    }
   }
  • startLocationServices: là function được gọi khi mình bắt đầu muốn lấy quyền truy cập location.Thường thì hay để ở hàm application(_ application, didFinishLaunchingWithOptions) trong AppDelegate hay khi mới vào bắt đầu vào main app
LocationManager.shared.startLocationServices()

Implement CLLocationManagerDelegate

  • Ở hàm startLocationServices : mình đã init CLLocationManager() và gắn delegate vào chính object này.Dó đó khi call LocationManager.shared.startLocationServices() , thì hàm delegate đầu tiên sẽ nhảy vào là :
 func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

        // check status
        switch status {
        case .notDetermined:
            manager.requestAlwaysAuthorization()
            break
        case .restricted:
            break
        case .denied:
            break
        case .authorizedAlways:
                      break
        case .authorizedWhenInUse:
                      break
        }
    }
  • Ở delegate này, tuỳ theo yêu cầu mà mình sẽ xin quyền là requestAlwaysAuthorization hay requestWhenInUseAuthorization.Ví dụ trên là manager.requestAlwaysAuthorization()

Get location

  • Tạo hàm để lấy location sau khi đã được cấp quyền location
func startStandardUpdates() {
        if CLLocationManager.locationServicesEnabled() &&
             CLLocationManager.authorizationStatus() != .denied {
            self.locationManager?.pausesLocationUpdatesAutomatically = true
            self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
            self.locationManager?.activityType = CLActivityType.otherNavigation
            self.locationManager?.distanceFilter = 10.0
            self.locationManager?.startUpdatingLocation()
        } else {
            self.startLocationServices()
        }
    }

    func stopStandardUpdates() {
        self.locationManager?.stopUpdatingLocation()
    }
  • Ở đây mình viết 2 hàm startStandardUpdates: để lấy bắt đầu lấy location và stopStandardUpdates: để stop.
  • Khi bắt đâu lấy location bằng
LocationManager.shared.startStandardUpdates()

thì 2 hàm delegate có thể đc call, đó là

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        // get location
        if let latestLocation = locations.last {
            let howRecent = latestLocation.timestamp.timeIntervalSinceNow

            if abs(howRecent) <= 5 {
                self.currentLocation = latestLocation
                self.stopStandardUpdates()
            } else {
                self.startStandardUpdates()
            }
        } else {
            self.startStandardUpdates()
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    }
  • ở delegate didUpdateLocations : do location có thể đc trả về là thông tin tại ví trí được get ở thời điểm trước đó, nên nó sẽ ko đc chính xác.Do đó, mình phải check latestLocation.timestamp.timeIntervalSinceNow của last location và kiểm tra thời gian lấy đó được phép trong khoảng bao nhiêu.Ở đây mình đg lấy là < 5s.
  • Thông tin của location lấy được sẽ lưu vào biến currentLocation và được dùng = LocationManager.shared.currentLocation

Lấy location khi thực hiện 1 action cụ thể

  • Việc lấy locaton là không động bộ, do đó, để biết khi nào location đc get là mới nhất thì cũng không dẽ dàng.
  • Ví dụ khi click vào button Login ở màn hình login, app yêu cầu lấy thông tin mới nhất của devive và gửi kèm với api.
  • Để thực hiện yêu cầu đó, mình làm như sau:
fileprivate var getCurrentLocationCallback: ((CLLocation?) -> Void)?
 // MARK: - Callback
    func getCurrentLocation(completion: ((_ location: CLLocation?) -> Void)?) {
        self.getCurrentLocationCallback = completion
        self.startStandardUpdates()
    }

    func stopGetCurrentLocation() {
        self.getCurrentLocationCallback = nil
        self.locationManager?.stopUpdatingLocation()
    }
 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        // get location
        if let latestLocation = locations.last {
            let howRecent = latestLocation.timestamp.timeIntervalSinceNow

            if abs(howRecent) <= 5 {
                self.currentLocation = latestLocation
                self.getCurrentLocationCallback?(latestLocation)
                self.stopStandardUpdates()
            } else {
                self.startStandardUpdates()
            }
        } else {
            self.startStandardUpdates()
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        self.getCurrentLocationCallback?(nil)
    }
  • Tạo 1 call back function và trả về khi lấy được dữ liệu location.
 LocationManager.shared.getCurrentLocation { (location) in
            LocationManager.shared.stopGetCurrentLocation()
            if let _location = location {
                // lastest location
            }
        }

Bắn notification khi lấy được latest location

  • Trong nhiều trường hơp, yêu cầu location đc update khi có sự thay đổi, thì thường sẽ tạo bắn ra notification khi get đc lastest location.
// MARK: - Notification
enum LocationEvent {
    static let didLocationDenied = "didLocationDeniedNotification"
     static let didLocationUpdate = "didLocationUpdateNotification"
}
if abs(howRecent) <= accuracy {
                self.currentLocation = latestLocation
                self.getCurrentLocationCallback?(latestLocation)
                self.stopStandardUpdates()

                // post notification
                let userInfo = ["key": currentLocation]
                NotificationCenter.default.post(name: Notification.Name(LocationEvent.didLocationUpdate), object: nil, userInfo: userInfo)

            }

Kết

Trên đây là cách sử dụng CLLocationManager mình thường dùng trong dự án.Hi vọng, nó giúp ích cho các bạn.