Đóng gói CLLocationManager
This post hasn't been updated for 7 years
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