Đóng gói CLLocationManager
Bài đăng này đã không được cập nhật trong 7 năm
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.
All rights reserved
Bình luận
Mình thắc mặc là tại sao phải notification để lấy thông tin lastest khi mà mỗi lần chuyển động là nó sẽ lấy đc thông tin rồi nhỉ? Hay ý bạn là thông tin mỗi lần chuyển động async nên ko lải là lastest? Cám ơn bài viết của bạn.
Bắn notification là 1 cách để có thể lắng nghe khi location thay đổi.
Bình thường LocationManager 1 khi đã start thì nó sẽ chạy cho đến khi bạn stop nó. vì nó được đóng gói trong singleton.
Vì thế cho nên trong bất kỳ ViewController nào bạn muốn biết location thay đổi hay ko thì hãy listen notification đó, mà ko cần khởi tạo CLLocationManager mới.
Mình quên mất biến currentLocation để lấy location khi cần thiết LocationManager.shared.currentLocation
var currentLocation: CLLocation?
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
anh có project mẫu không dạ em xin xem với
đây bạn https://github.com/ltranframgia/Core/blob/master/CoreProject/Utitlities/Core/Managers/LocationManager.swift
em cảm ơn anh nhe