iOS開發 - CoreLocation地理定位
簡介
在移動互連網時代,移動app能解決使用者的很多生活瑣事,比如
導航:去任意陌生的地方
周邊:找餐館、找酒店、找銀行、找電影院
在上述應用中,都用到了地圖和定位功能,在iOS開發中,要想加入這2大功能,必須基於2個架構進行開發
Map Kit :用於地圖展示
Core Location :用於地理定位
2個熱門專業術語
LBS :Location Based Service
SoLoMo :Social Local Mobile(索羅門)
CoreLocation架構的使用
CoreLocation架構使用前提
匯入架構
匯入主標頭檔
#import
CoreLocation架構使用須知
CoreLocation架構中所有資料類型的首碼都是CL
CoreLocation中使用CLLocationManager對象來做使用者定位
CLLocationManager
CLLocationManager的常用操作//開始使用者定位- (void)startUpdatingLocation;//停止使用者定位- (void) stopUpdatingLocation;//當調用了startUpdatingLocation方法後,就開始不斷地定位使用者的位置,中途會頻繁地調用代理的下面方法- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;locations參數裡面裝著CLLocation對象@property(assign, nonatomic) CLLocationDistance distanceFilter;//每隔多少米定位一次@property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;//定位精確度(越精確就越耗電)
CLLocation
CLLocation用來表示某個位置的地理資訊,比如經緯度、海拔等等
@property(readonly, nonatomic) CLLocationCoordinate2D coordinate;//經緯度@property(readonly, nonatomic) CLLocationDistance altitude;//海拔@property(readonly, nonatomic) CLLocationDirection course;//路線,航向(取值範圍是0.0° ~ 359.9°,0.0°代表真北方向)@property(readonly, nonatomic) CLLocationSpeed speed;//行走速度(單位是m/s)用- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location方法可以計算2個位置之間的距離
CLLocationCoordinate2D
CLLocationCoordinate2D是一個用來表示經緯度的結構體,定義如下typedef struct { CLLocationDegrees latitude; // 緯度 CLLocationDegrees longitude; // 經度} CLLocationCoordinate2D;//一般用CLLocationCoordinate2DMake
使用者隱私的保護
從iOS 6開始,蘋果在保護使用者隱私方面做了很大的加強,以下操作都必須經過使用者批准授權
要想獲得使用者的位置
想訪問使用者的通訊錄、日曆、相機、相簿等等
當想訪問使用者的隱私資訊時,系統會自動彈出一個對話方塊讓使用者授權
開發人員可以在Info.plist中設定NSLocationUsageDescription說明定位的目的(Privacy - Location Usage Description)
一旦使用者選擇了“Don’t Allow”,意味著你的應用以後就無法使用定位功能
為了嚴謹起見,最好在使用定位功能之前判斷當前應用的定位功能是否可用
//CLLocationManager有個類方法可以判斷當前應用的定位功能是否可用
+ (BOOL)locationServicesEnabled;
CLGeocoder
使用CLGeocoder可以完成“地理編碼”和“反地理編碼”
地理編碼:根據給定的地名,獲得具體的位置資訊(比如經緯度、地址的全稱等)
反地理編碼:根據給定的經緯度,獲得具體的位置資訊
//地理編碼方法- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;//反地理編碼方法- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
CLGeocodeCompletionHandler
//當地理反地理編碼完成時,就會調用CLGeocodeCompletionHandlertypedef void (^CLGeocodeCompletionHandler)(NSArray *placemarks, NSError *error);這個block傳遞2個參數error :當編碼出錯時(比如編碼不出具體的資訊)有值placemarks :裡面裝著CLPlacemark對象
標題
CLPlacemark的字面意思是地標,封裝詳細的地址位置資訊
@property (nonatomic, readonly) CLLocation *location;//地理位置@property (nonatomic, readonly) CLRegion *region;//地區@property (nonatomic, readonly) NSDictionary *addressDictionary;//詳細的地址資訊@property (nonatomic, readonly) NSString *name;//位址名稱@property (nonatomic, readonly) NSString *locality;//城市
關係圖
CoreLocation執行個體
@interface ViewController ()/** * 定位管理者 */@property (nonatomic ,strong) CLLocationManager *mgr;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 1.建立CoreLocation管理者 CLLocationManager *mgr = [[CLLocationManager alloc] init]; // 2.成為CoreLocation管理者的代理監聽擷取到的位置 self.mgr.delegate = self; // 設定多久擷取一次 self.mgr.distanceFilter = 500; // 設定擷取位置的精確度 /* kCLLocationAccuracyBestForNavigation 最佳導航 kCLLocationAccuracyBest; 最精準 kCLLocationAccuracyNearestTenMeters; 10米 kCLLocationAccuracyHundredMeters; 百米 kCLLocationAccuracyKilometer; 千米 kCLLocationAccuracyThreeKilometers; 3千米 */ self.mgr.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; // 判斷是否是iOS8 if([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) { NSLog(@是iOS8); // 主動要求使用者對我們的程式授權, 授權狀態改變就會通知代理 // [self.mgr requestAlwaysAuthorization]; // 請求前台和後台定位許可權// [self.mgr requestWhenInUseAuthorization];// 請求前台定位許可權 }else { NSLog(@是iOS7); // 3.開始監聽(開始擷取位置) [self.mgr startUpdatingLocation]; }}/** * 授權狀態發生改變時調用 * * @param manager 觸發事件的對象 * @param status 當前授權的狀態 */- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{ /* 使用者從未選擇過許可權 kCLAuthorizationStatusNotDetermined 無法使用定位服務,該狀態使用者無法改變 kCLAuthorizationStatusRestricted 使用者拒絕該應用使用定位服務,或是定位服務總開關處於關閉狀態 kCLAuthorizationStatusDenied 已經授權(廢棄) kCLAuthorizationStatusAuthorized 使用者允許該程式無論何時都可以使用地理資訊 kCLAuthorizationStatusAuthorizedAlways 使用者同意程式在可見時使用地理位置 kCLAuthorizationStatusAuthorizedWhenInUse */ if (status == kCLAuthorizationStatusNotDetermined) { NSLog(@等待使用者授權); }else if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) { NSLog(@授權成功); // 開始定位 [self.mgr startUpdatingLocation]; }else { NSLog(@授權失敗); }}#pragma mark - CLLocationManagerDelegate//- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation/** * 擷取到位置資訊之後就會調用(調用頻率非常高) * * @param manager 觸發事件的對象 * @param locations 擷取到的位置 */- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ NSLog(@%s, __func__); // 如果只需要擷取一次, 可以擷取到位置之後就停止// [self.mgr stopUpdatingLocation];}#pragma mark - 懶載入- (CLLocationManager *)mgr{ if (!_mgr) { _mgr = [[CLLocationManager alloc] init]; } return _mgr;}@end
/* 注意: iOS7隻要開始定位, 系統就會自動要求使用者對你的應用程式授權. 但是從iOS8開始, 想要定位必須先自己主動要求使用者授權 在iOS8中不僅僅要主動請求授權, 而且必須再info.plist檔案中配置一項屬性才能彈出授權視窗 NSLocationWhenInUseDescription,允許在前台擷取GPS的描述 NSLocationAlwaysUsageDescription,允許在後台擷取GPS的描述 */
@interface ViewController ()/** * 定位管理者 */@property (nonatomic ,strong) CLLocationManager *mgr;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 1.建立CoreLocation管理者// CLLocationManager *mgr = [[CLLocationManager alloc] init]; // 2.成為CoreLocation管理者的代理監聽擷取到的位置 self.mgr.delegate = self; // 判斷是否是iOS8 if([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) { NSLog(@是iOS8); // 主動要求使用者對我們的程式授權, 授權狀態改變就會通知代理 [self.mgr requestAlwaysAuthorization]; // 請求前台和後台定位許可權 }else { NSLog(@是iOS7); // 3.開始監聽(開始擷取位置) [self.mgr startUpdatingLocation]; }}/** * 授權狀態發生改變時調用 * * @param manager 觸發事件的對象 * @param status 當前授權的狀態 */- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{ if (status == kCLAuthorizationStatusNotDetermined) { NSLog(@等待使用者授權); }else if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) { NSLog(@授權成功); // 開始定位 [self.mgr startUpdatingLocation]; }else { NSLog(@授權失敗); }}#pragma mark - CLLocationManagerDelegate/** * 擷取到位置資訊之後就會調用(調用頻率非常高) * * @param manager 觸發事件的對象 * @param locations 擷取到的位置 */- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ NSLog(@%s, __func__); // 如果只需要擷取一次, 可以擷取到位置之後就停止// [self.mgr stopUpdatingLocation]; // 1.擷取最後一次的位置 /* location.coordinate; 座標, 包含經緯度 location.altitude; 裝置海拔高度 單位是米 location.course; 設定前進方向 0表示北 90東 180南 270西 location.horizontalAccuracy; 水平精準度 location.verticalAccuracy; 垂直精準度 location.timestamp; 定位資訊返回的時間 location.speed; 裝置移動速度 單位是米/秒, 適用於行車速度而不太適用於不行 */ /* 可以設定模擬器類比速度 bicycle ride 騎車移動 run 跑動 freeway drive 高速公路駕車 */ CLLocation *location = [locations lastObject]; NSLog(@%f, %f speed = %f, location.coordinate.latitude , location.coordinate.longitude, location.speed);}#pragma mark - 懶載入- (CLLocationManager *)mgr{ if (!_mgr) { _mgr = [[CLLocationManager alloc] init]; } return _mgr;}@end
汽車導航執行個體
#import ViewController.h#import @interface ViewController ()// 建立定位管理者@property (nonatomic, strong) CLLocationManager *mgr;// 上一次的位置@property (nonatomic, strong) CLLocation *previousLocation;// 總路程@property (nonatomic, assign) CLLocationDistance sumDistance;// 總時間@property (nonatomic, assign) NSTimeInterval sumTime;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 1.建立定位管理者// CLLocationManager *mgr = [[CLLocationManager alloc] init]; // 2.設定代理 self.mgr.delegate = self; // 3.判斷是否是iOS8 if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) { // 主動請求授權 [self.mgr requestAlwaysAuthorization]; } // 3.開始定位 [self.mgr startUpdatingLocation];}#pragma mark - CLLocationManagerDelegate- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{// CLLocation; timestamp 當前擷取到為止資訊的時間 /* 擷取走了多遠(這一次的位置 減去上一次的位置) 擷取走這段路花了多少時間 (這一次的時間 減去上一次的時間) 擷取速度 (走了多遠 / 花了多少時間) 擷取總共走的路程 (把每次擷取到走了多遠累加起來) 擷取平均速度 (用總路程 / 總時間) */ // 擷取當前的位置 CLLocation *newLocation = [locations lastObject]; if (self.previousLocation != nil) { // 計算兩次的距離(單位時米) CLLocationDistance distance = [newLocation distanceFromLocation:self.previousLocation]; // 計算兩次之間的時間(單位只秒) NSTimeInterval dTime = [newLocation.timestamp timeIntervalSinceDate:self.previousLocation.timestamp]; // 計算速度(米/秒) CGFloat speed = distance / dTime; // 累加時間 self.sumTime += dTime; // 累加距離 self.sumDistance += distance; // 計算平均速度 CGFloat avgSpeed = self.sumDistance / self.sumTime; NSLog(@距離%f 時間%f 速度%f 平均速度%f 總路程 %f 總時間 %f, distance, dTime, speed, avgSpeed, self.sumDistance, self.sumTime); } // 紀錄上一次的位置 self.previousLocation = newLocation;}//#pragma mark - 懶載入- (CLLocationManager *)mgr{ if (!_mgr) { self.mgr = [[CLLocationManager alloc] init]; } return _mgr;}@end
指南針執行個體
#import ViewController.h#import @interface ViewController ()/** * 定位管理者 */@property (nonatomic ,strong) CLLocationManager *mgr;// 指南針圖片@property (nonatomic, strong) UIImageView *compasspointer;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 1.添加指南針圖片 UIImageView *iv = [[UIImageView alloc] initWithImage: [UIImage imageNamed:@bg_compasspointer]]; iv.center = CGPointMake(self.view.center.x, self.view.center.y); [self.view addSubview:iv]; self.compasspointer = iv; // 2.成為CoreLocation管理者的代理監聽擷取到的位置 self.mgr.delegate = self; // 3.開始擷取使用者位置 // 注意:擷取使用者的方向資訊是不需要使用者授權的 [self.mgr startUpdatingHeading];}#pragma mark - CLLocationManagerDelegate// 當擷取到使用者方向時就會調用- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{// NSLog(@%s, __func__); /* magneticHeading 裝置與磁北的相對角度 trueHeading 設定與真北的相對角度, 必須和定位一起使用, iOS需要設定的位置來計算真北 真北始終指向地理北極點 */// NSLog(@%f, newHeading.magneticHeading); // 1.將擷取到的角度轉為弧度 = (角度 * π) / 180; CGFloat angle = newHeading.magneticHeading * M_PI / 180; // 2.旋轉圖片 /* 順時針 正 逆時針 負數 */// self.compasspointer.transform = CGAffineTransformIdentity; self.compasspointer.transform = CGAffineTransformMakeRotation(-angle);}#pragma mark - 懶載入- (CLLocationManager *)mgr{ if (!_mgr) { _mgr = [[CLLocationManager alloc] init]; } return _mgr;}@end
地區檢查執行個體
#import ViewController.h#import @interface ViewController ()/** * 定位管理者 */@property (nonatomic ,strong) CLLocationManager *mgr;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 2.成為CoreLocation管理者的代理監聽擷取到的位置 self.mgr.delegate = self; // 注意:如果是iOS8, 想進列區域檢測, 必須自己主動請求擷取使用者隱私的許可權 if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0 ) { [self.mgr requestAlwaysAuthorization]; } // 3.開始檢測使用者所在的地區 // 3.1建立地區// CLRegion 有兩個子類是專門用於指定地區的// 一個可以指定藍芽的範圍/ 一個是可以指定圓形的範圍 // 建立中心點 CLLocationCoordinate2D center = CLLocationCoordinate2DMake(40.058501, 116.304171); // c建立圓形地區, 指定地區中心點的經緯度, 以及半徑 CLCircularRegion *circular = [[CLCircularRegion alloc] initWithCenter:center radius:500 identifier:@軟體園]; [self.mgr startMonitoringForRegion:circular];}#pragma mark - CLLocationManagerDelegate// 進入監聽地區時調用- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{ NSLog(@進入監聽地區時調用);}// 離開監聽地區時調用- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{ NSLog(@離開監聽地區時調用);}//#pragma mark - 懶載入- (CLLocationManager *)mgr{ if (!_mgr) { _mgr = [[CLLocationManager alloc] init]; } return _mgr;}@end
地理編碼執行個體
#import ViewController.h#import @interface ViewController ()#pragma mark - 地理編碼/** * 監聽地理編碼點擊事件 */- (IBAction)geocodeBtnClick;/** * 需要編碼的地址容器 */@property (weak, nonatomic) IBOutlet UITextField *addressField;/** * 經度容器 */@property (weak, nonatomic) IBOutlet UILabel *longitudeLabel;/** * 緯度容器 */@property (weak, nonatomic) IBOutlet UILabel *latitudeLabel;/** * 詳情容器 */@property (weak, nonatomic) IBOutlet UILabel *detailAddressLabel;/** * 地理編碼對象 */@property (nonatomic ,strong) CLGeocoder *geocoder;@end@implementation ViewController- (void)geocodeBtnClick{ // 0.擷取使用者輸入的位置 NSString *addressStr = self.addressField.text; if (addressStr == nil || addressStr.length == 0) { NSLog(@請輸入地址); return; } // 1.建立地理編碼對象 // 2.利用地理編碼對象編碼 // 根據傳入的地址擷取該地址對應的經緯度資訊 [self.geocoder geocodeAddressString:addressStr completionHandler:^(NSArray *placemarks, NSError *error) { if (placemarks.count == 0 || error != nil) { return ; } // placemarks地標數組, 地標數組中存放著地標, 每一個地標包含了該位置的經緯度以及城市/地區/國家代碼/郵編等等... // 擷取數組中的第一個地標 CLPlacemark *placemark = [placemarks firstObject];// for (CLPlacemark *placemark in placemarks) {// NSLog(@%@ %@ %f %f, placemark.name, placemark.addressDictionary, placemark.location.coordinate.latitude, placemark.location.coordinate.longitude); self.latitudeLabel.text = [NSString stringWithFormat:@%f, placemark.location.coordinate.latitude]; self.longitudeLabel.text = [NSString stringWithFormat:@%f, placemark.location.coordinate.longitude]; NSArray *address = placemark.addressDictionary[@FormattedAddressLines]; NSMutableString *strM = [NSMutableString string]; for (NSString *str in address) { [strM appendString:str]; } self.detailAddressLabel.text = strM;// } }];}- (CLGeocoder *)geocoder{ if (!_geocoder) { _geocoder = [[CLGeocoder alloc] init]; } return _geocoder;}@end
反地理編碼執行個體
#import ViewController.h#import @interface ViewController ()/** * 地理編碼對象 */@property (nonatomic ,strong) CLGeocoder *geocoder;#pragma mark - 反地理編碼- (IBAction)reverseGeocode;@property (weak, nonatomic) IBOutlet UITextField *longtitudeField;@property (weak, nonatomic) IBOutlet UITextField *latitudeField;@property (weak, nonatomic) IBOutlet UILabel *reverseDetailAddressLabel;@end@implementation ViewController- (void)reverseGeocode{ // 1.擷取使用者輸入的經緯度 NSString *longtitude = self.longtitudeField.text; NSString *latitude = self.latitudeField.text; if (longtitude.length == 0 || longtitude == nil || latitude.length == 0 || latitude == nil) { NSLog(@請輸入經緯度); return; } // 2.根據使用者輸入的經緯度建立CLLocation對象 CLLocation *location = [[CLLocation alloc] initWithLatitude:[latitude doubleValue] longitude:[longtitude doubleValue]]; // 3.根據CLLocation對象擷取對應的地標資訊 [self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) { for (CLPlacemark *placemark in placemarks) { NSLog(@%@ %@ %f %f, placemark.name, placemark.addressDictionary, placemark.location.coordinate.latitude, placemark.location.coordinate.longitude); self.reverseDetailAddressLabel.text = placemark.locality; } }];}//#pragma mark - 懶載入- (CLGeocoder *)geocoder{ if (!_geocoder) { _geocoder = [[CLGeocoder alloc] init]; } return _geocoder;}@end