前言:
本章會使用OC和Swift分別進行實現,需要了解Swift的小伙伴可以翻一下之前的博文
distanceFilter(距離過濾):最新位置距上次位置之間距離大於這個值,就會告訴通過代理告訴外界
默認距離KCLDistanceFilterNone (值為-1,因為小於0,所以會一直打印) 單位:米desiredAccuracy(定位精確度):定位精確度越高,定位時間就越長,也就越耗電
kCLLocationAccuracyBestForNavigation // 最適合導航 kCLLocationAccuracyBest // 最好的 kCLLocationAccuracyNearestTenMeters; // 附近10米 kCLLocationAccuracyHundredMeters; // 附近100米 kCLLocationAccuracyKilometer; // 附近1000米 kCLLocationAccuracyThreeKilometers; // 附近3000米
// 為了全局只使用一個位置管理者,我們先對CLLocationManager進行懶加載
- (CLLocationManager *)locationM {
if (_locationM == nil) {
// 創建位置管理者
_locationM = [[CLLocationManager alloc] init];
// 設置代理
_locationM.delegate = self;
}
return _locationM;
}
// 在按鈕點擊事件中開啟定位服務
// start:開啟服務 stop:關閉服務
// 一旦調用這個方法,就會不斷的調用用戶信息(因為distanceFilter屬性的默認值為-1)
// 基本定位(基於Wifi/GPS)
[self.locationM startUpdatingLocation];
// 這個方法是用來監聽重大位置改變(因為基站與基站之間相距大所以這個方法會通過基站進行定位,前提是有電話模塊的支持)
// [self.locationM startMonitoringSignificantLocationChanges];
// 先遵守CLLocationManagerDelegate協議,實現下面代理方法
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(@"已定位到");
// 定位是非常頻繁的,所以獲取到用戶信息後,最好馬上關閉停止定位,以達到省電效果,在適當的時候再重新打開定位
[manager stopUpdatingLocation];
self.locationM = nil;
}
Swift:
// MARK:- 懶加載
private lazy var locationM : CLLocationManager = {
// 創建位置管理者
let locationM = CLLocationManager()
// 設置代理
locationM.delegate = self
return locationM
}()
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
// 使用位置管理者獲取用戶位置信息
// 根據蘋果的習慣,一般方法命中帶ing(現在進行時),說明一旦執行這個方法,系統就會不斷的調用這個方法
// 默認情況下只會在前台進行定位,如果在後台也想要獲取用戶的位置,需要開啟後台模式 location updates
locationM.startUpdatingLocation()
}
// MARK:- CLLocationManagerDelegate
extension ViewController : CLLocationManagerDelegate {
// manager : 位置管理者
// locations : 位置數組
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("已定位到")
// 關閉定位
manager.stopUpdatingLocation()
}
如果想要在後台繼續進行定位,需要打開後台的定位模式
vc+1zaOsusS158G/0KGjrLj80MK1xMa1wsq4+b7dtbHHsM671sO4vb38tcS7+dW+w9y2yL72tqgNCjxociAvPg0KPGgyIGlkPQ=="ios8之後定位">iOS8之後定位
從iOS8開始,蘋果進一步加強了對用戶隱私的保護,當APP想范圍用戶隱私信息的時候,系統不再自動彈出對話框讓用戶授權,為了能讓系統自動彈出用戶授權界面,需要進行下面設置
解決方案:調用iOS8的API,主動請求用戶授權
// 注意:根據官方文檔的解釋,在使用下面2個方法的時候,如果不在info.plist中配置NSLocationWhenInUseUsageDescription這個key,那麼方法都不會生效
// 請求前台定位授權
- (void)requestWhenInUseAuthorization
// 注意:根據官方文檔的解釋,在使用下面2個方法的時候,如果不在info.plist中配置`NSLocationAlwaysUsageDescription`這個key,那麼方法都不會生效
// 請求前後台定位授權
- (void)requestAlwaysAuthorization
OC:
- (CLLocationManager *)manager
{
if (_manager == nil) {
_manager = [[CLLocationManager alloc] init];
_manager.delegate = self;
// 需要注意的是,必須在info.plist文件中配置’NSLocationWhenInUseUsageDescription‘這個key,否則下面方法無效(官方注釋有提到)
// 請求前台授權
[_manager requestWhenInUseAuthorization];
// 請求前後台授權
// [_manager requestAlwaysAuthorization];
}
return _manager;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.manager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(@"定位到了");
[self.manager stopUpdatingLocation];
}
swift:
class ViewController: UIViewController {
lazy var locationMgr : CLLocationManager = {
let locationMgr = CLLocationManager()
locationMgr.delegate = self
// 記得設置相應的授權請求Key
// 請求前台定位授權
locationMgr.requestWhenInUseAuthorization()
// 請求前後台定位授權
locationMgr.requestAlwaysAuthorization()
return locationMgr
}()
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
// 開啟定位
locationMgr.startUpdatingLocation()
}
}
//MARK: - CLLocationManager代理
extension ViewController : CLLocationManagerDelegate {
// 當定位到位置後調用
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("定位到了")
manager.stopUpdatingLocation()
}
}
OC:
if ([_manager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
// 需要注意的是,必須在info.plist文件中配置’NSLocationWhenInUseUsageDescription‘這個key,否則下面方法無效(官方注釋有提到)
// 請求前台授權
[_manager requestWhenInUseAuthorization];
}
if ([_manager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
// 請求前後台授權(無論是否開啟後台模式都可以獲取位置信息,並且不會出現藍條提示)
[_manager requestAlwaysAuthorization];
}
swift:
// 記得設置相應的授權請求Key
// 根據當前系統版本適配
// 當前版本是8.0及以上
if #available(iOS 8.0, *) {
// 請求前台定位授權
locationMgr.requestWhenInUseAuthorization()
}
if #available(iOS 8.0, *) {
// 請求前後台定位授權
locationMgr.requestAlwaysAuthorization()
}
iOS9定位變化OC:
// 當授權狀態發生改變時調用
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
switch (status) {
case kCLAuthorizationStatusNotDetermined:
NSLog(@"用戶未選擇");
break;
// 暫時沒用,應該是蘋果預留接口
case kCLAuthorizationStatusRestricted:
NSLog(@"受限制");
break;
// 真正被拒絕、定位服務關閉等影響定位服務的行為都會進入被拒絕狀態
case kCLAuthorizationStatusDenied:
if ([CLLocationManager locationServicesEnabled]) { // 定位服務開啟
NSLog(@"真正被用戶拒絕");
// 跳轉到設置界面
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) { // url地址可以打開
[[UIApplication sharedApplication] openURL:url];
}
} else {
NSLog(@"服務未開啟");
}
break;
case kCLAuthorizationStatusAuthorizedAlways:
NSLog(@"前後台定位授權");
break;
case kCLAuthorizationStatusAuthorizedWhenInUse:
NSLog(@"前台定位授權");
break;
default:
break;
}
}
swift:
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
print("用戶未選擇")
case .Restricted:
print("受限制")
case.Denied:
print("被拒絕")
if CLLocationManager .locationServicesEnabled() { // 定位服務開啟
print("用戶真正拒絕")
// 跳轉到設置界面
if #available(iOS 8.0, *) {
let url = NSURL(string: UIApplicationOpenSettingsURLString)
if UIApplication.sharedApplication().canOpenURL(url!) {
UIApplication.sharedApplication().openURL(url!)
}
}
} else {
print("服務未開啟")
}
case .AuthorizedAlways:
print("前後台定位授權")
case .AuthorizedWhenInUse:
print("前台定位授權")
}
}
// 獲取當前位置信息
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// locations內的元素是按時間順序排列,所以要獲取最新的位置信息直接取locations數組內的最後一個元素即可(蘋果官方文檔注釋)
NSLog(@"%@", [locations lastObject]);
}
結果:維度、經度、海拔(負值表示當前海拔無效)速度(負)航向(從0~359.9) 位置時間
根據獲取的位置信息計算用戶行走方向,行走距離,偏移角度
coordinate:經緯度信息 altitude:海拔 horizontalAccuracy:水平方向精度,值為負數時,表示無效 verticalAccuracy:判斷海拔是否為負數,負數無效 course:航向(0~359.9) floor:樓層(使用的樓層需要注冊,否則無法使用) distanceFromLocation:計算2點之間的物理直線距離OC:
// 獲取當前位置信息
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
/*
* coordinate:經緯度信息
* altitude:海拔
* horizontalAccuracy:水平方向精確度,值如果小於0,代表位置數據無效
* verticalAccuracy:判斷海拔是否為負數,負數無效
* floor:樓層,使用的樓層需要注冊,否則無法使用
* course:航向(0~359.9)(這裡的0表示的是正北不是磁北)
* distanceFromLocation:計算2各店之間物理直線距離
*/
// 獲取當前位置信息
CLLocation *locationC = locations.lastObject;
// 判斷水平數據是否有效
if (locationC.horizontalAccuracy < 0) { // 負數表示無效
return;
}
// 計算行走方向(北偏東,東偏南,南偏西,西偏北)
NSArray *courseAry = @[@"北偏東", @"東偏南", @"南偏西", @"西偏北"];
// 將當前航向值/90度會得到對應的值(0,1,2,3)
NSInteger i = locationC.course / 90;
// 取出對應航向
NSString *courseStr = courseAry[i];
// 計算偏移角度
NSInteger angle = (int)locationC.course % 90;
// 判斷是否為正方向
// 對角度取余,為0表示正
if (angle == 0) {
// 截取字符串第一個字
courseStr = [courseStr substringToIndex:1];
// 拼接字符串
courseStr = [@"正" stringByAppendingString:courseStr];
}
// 計算移動多少米
CGFloat distance = [locationC distanceFromLocation:self.lastLocation];
// 記錄上次距離
self.lastLocation = locationC;
NSLog(@"向 %@ 方向走了 %lf 米偏移角度 %ld 度", courseStr, distance, angle);
}
swift:
// 當定位到位置後調用
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// 獲取用戶當前最新位置
let locationC = locations.last
// 判斷水平數據是否有效
if locationC?.horizontalAccuracy < 0 { // 負數表示無效
return
}
// 計算行走方向(北偏東,東偏南,南偏西,西偏北)
let courseAry = ["北偏東", "東偏南", "南偏西", "西偏北"]
// 將當前航向值/90度會得到相應的值(0,1,2,3)
let i = Int((locationC?.course)! / 90)
// 取出對應航向
var courseStr = courseAry[i]
// 計算偏移角度
let angle = Int((locationC?.course)! % 90)
// 判斷是否為正方向
// 對角度取余,為0就表示正
if Int(angle) == 0 {
// 截取字符串第一個字
courseStr = (courseStr as NSString).substringToIndex(1)
}
// 確定移動距離
let lastLoc = lastLocation ?? locationC
let distance = locationC?.distanceFromLocation(lastLoc!)
lastLocation = locationC
// 拼接字符串
print("向\(courseStr)方向走了\(distance!)米偏移角度\(angle)")
}
OC:
- (CLLocationManager *)manager
{
if (!_manager) {
_manager = [[CLLocationManager alloc] init];
_manager.delegate = self;
// 請求用戶授權區域監聽
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
[_manager requestAlwaysAuthorization];
}
}
return _manager;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 判斷區域監聽是否可用
if ([CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
return;
}
// 創建一個區域
// 確定圓心
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(21.23, 123.345);
// 確定半徑
CLLocationDistance distance = 1000.0;
// 因為監聽區域有最大值,所以要判斷下是否超過監聽的最大值
if (distance > self.manager.maximumRegionMonitoringDistance) {
distance = self.manager.maximumRegionMonitoringDistance;
}
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:distance identifier:@"123"];
// 開始監聽區域
[self.manager startMonitoringForRegion:region];
}
// 進入區域時
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(@"進入區域%@",region.identifier);
}
// 離開區域時
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(@"離開區域%@",region.identifier);
}
// 但外界調用請求某個指定區域的狀態時
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateUnknown)
{
NSLog(@"未識別");
}
if (state == CLRegionStateInside) {
NSLog(@"在區域內");
}
if (state == CLRegionStateOutside) {
NSLog(@"在區域外");
}
}
swift:
lazy var locationMgr : CLLocationManager = {
let locationMgr = CLLocationManager()
locationMgr.delegate = self
// 記得設置相應的授權請求Key
// 當前版本是8.0及以上
if #available(iOS 8.0, *) {
// 請求前後台定位授權
locationMgr.requestAlwaysAuthorization()
}
return locationMgr
}()
override func viewDidLoad() {
super.viewDidLoad()
// 創建一個區域
// 確定圓心
let center = CLLocationCoordinate2DMake(21.23, 123.345)
// 確定半徑
var distance : CLLocationDistance = 1000
// 因為監聽區域有最大值,索引先判斷是否超過了監聽區域的最大值
if distance > locationMgr.maximumRegionMonitoringDistance {
distance = locationMgr.maximumRegionMonitoringDistance
}
let region = CLCircularRegion(center: center, radius: distance, identifier: "123")
// 判斷取余監聽是否可用
if CLLocationManager.isMonitoringAvailableForClass(region.classForCoder) {
// 開始監聽區域
locationMgr.startMonitoringForRegion(region)
}
}
// 進入區域
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("進入監聽區域")
}
// 離開區域
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
print("離開監聽區域")
}
// 區域狀態改變
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
if state == .Unknown {
print("未識別")
}
if state == .Inside {
print("在區域內")
}
if state == .Outside {
print("在區域外")
}
}
注意:OC:
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
// 地理編碼
[geocoder geocodeAddressString:@"福建省廈門市" completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
CLPlacemark *placeM = [placemarks lastObject];
NSLog(@"維度:%@ -- 經度:%@", @(placeM.location.coordinate.latitude).stringValue, @(placeM.location.coordinate.longitude).stringValue);
}];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
// 反地理編碼
CLLocationDegrees latitude = 24.490474;
CLLocationDegrees longitude = 118.11022;
CLLocation *location = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {
NSLog(@"地址:%@", [placemarks firstObject].name);
}];
swift:
let geocoder = CLGeocoder()
// 地理編碼
geocoder.geocodeAddressString("福建省廈門市") { (placemarks, error) in
let placeM = placemarks?.last
print("維度\(placeM?.location?.coordinate.latitude) -- 經度\(placeM?.location?.coordinate.longitude)")
}
// 反地理編碼
let latitude : CLLocationDegrees = 24.490474
let longitude : CLLocationDegrees = 118.11022
let location = CLLocation(latitude: latitude, longitude: longitude)
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
print("地址:\(placemarks?.first?.name)")
}
}