編輯:關於Android編程
現在很多社交、電商、團購應用都引入了地圖和定位功能,似乎地圖功能不再是地圖應用和導航應用所特有的。的確,有了地圖和定位功能確實讓我們的生活更加豐富多彩,極大的改變了我們的生活方式。例如你到了一個陌生的地方想要查找附近的酒店、超市等就可以打開軟件搜索周邊;類似的,還有很多團購軟件可以根據你所在的位置自動為你推薦某些商品。總之,目前地圖和定位功能已經大量引入到應用開發中。在產品研發中有兩個專業術語需要大家知道:一是LBS(Location Based Service)基於定位的服務,二是SoLoMo(Social Local Mobile )社交本地的移動應用,這都是需要我們提供地圖和定位服務。
在後面的課程裡就和大家一起看一下iOS如何進行地圖和定位開發。iOS系統為了方便我們開發,提供了地圖服務的框架。除此之外,實際的開發工作中,我們常常會使用一些第三發的SDK來實現地圖服務,主要有高德地圖和百度地圖,這些我們後在後面的幾節中講到。
地圖功能的實現離不開定位服務,下面我們還是先來看一下iOS系統的定位功能是如何實現的。
要實現地圖、導航功能,往往需要先熟悉定位功能,iOS中的定位引擎是CoreLocation框架提供的,我們通過CoreLocation框架進行定位操作。CoreLocation自身可以單獨使用,和地圖開發框架MapKit完全是獨立的,但是往往地圖開發要配合定位框架使用。在Core Location中主要包含了定位、地理編碼(包括反編碼)功能。
我們先介紹一下iOS定位功能的實現。定位是一個很常用的功能,如一些地圖軟件打開之後如果用戶允許軟件定位的話,那麼打開軟件後就會自動鎖定到當前位置,如果用戶手機移動那麼當前位置也會跟隨著變化。要實現這個功能需要使用CoreLoaction中CLLocationManager類,首先看一下這個類的一些主要方法和屬性:
類方法
說明
(BOOL)locationServicesEnabled;是否啟用定位服務,通常如果用戶沒有啟用定位服務可以提示用戶打開定位服務
(CLAuthorizationStatus)authorizationStatus;定位服務授權狀態,返回枚舉類型:
kCLAuthorizationStatusNotDetermined: 用戶尚未做出決定是否啟用定位服務
kCLAuthorizationStatusRestricted: 沒有獲得用戶授權使用定位服務,可能用戶沒有自己禁止訪問授權
kCLAuthorizationStatusDenied :用戶已經明確禁止應用使用定位服務或者當前系統定位服務處於關閉狀態
kCLAuthorizationStatusAuthorizedAlways: 應用獲得授權可以一直使用定位服務,即使應用不在使用狀態
kCLAuthorizationStatusAuthorizedWhenInUse: 使用此應用過程中允許訪問定位服務
除了CLLocationManager之外,CLLocation類也是在我們做定位服務中經常看到的,CLLocation常用來表示某個位置的地理信息,比如經緯度、海拔高度等,當然他也給我們提供了計算兩個地理位置之間間距的方法。下面我們看一下CLLocation的常用屬性和方法:
一般在開始定位之前,應用會向用戶獲取授權請求,在iOS7及以前的版本,如果在應用程序中使用定位服務只要在程序中調用startUpdatingLocation方法應用就會詢問用戶是否允許此應用是否允許使用定位服務,同時在提示過程中可以通過在info.plist中配置通過配置Privacy -Location Usage Description告訴用戶使用的目的,當然這個配置是可選的。但是在iOS8中配置配置項發生了變化,我們可以通過配置NSLocationAlwaysUsageDescription或者 NSLocationWhenInUseUsageDescription來告訴用戶使用定位服務的目的,並且注意這個配置是必須的,如果不進行配置則默認情況下應用無法使用定位服務,打開應用不會給出打開定位服務的提示,除非安裝後自己設置此應用的定位服務。同時,在應用程序中需要根據配置對requestAlwaysAuthorization或locationServicesEnabled方法進行請求。
iOS8提供了更加人性化的定位服務選項。應用的定位服務不再僅僅是關閉或打開。現在,定位服務的啟用提供了三個選項:永不、使用應用程序期間、和始終。同時,考慮到能耗問題,如果一款 App 要求始終能在後台開啟定位服務,iOS 8 不僅會在首次打開 App 時主動向你詢問,還會在日常使用中彈窗提醒你該 App 一直在後台使用定位服務,並詢問你是否繼續允許。
下面我們就來看一看iOS實現定位服務的具體步驟有哪些
示例代碼
#import "ViewController.h"
#import
@interface ViewController ()
// 定位服務管家
@property (nonatomic, strong)CLLocationManager *locationManager;
@end
@implementation ViewController
/*定位的實現
1.導入框架 CoreLocation.framework
2.導入庫文件 #import
3.需要將定位管家 CLLocationManager 設置為全區變量
1.判斷硬件是否開啟了定位服務
2.初始化定位管家的對象,注意需要設置為全局變量
3.判斷定位服務授權狀態,設置定位權限:iOS8的新特性,可以實現代理方法獲取授權范圍。注意需要修改plist文件 NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。
4.設置定位服務的屬性。
5.開啟定位,實現代理方法,獲取定位信息
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 1.判斷硬件是否開啟了定位服務
BOOL isOpen = [CLLocationManager locationServicesEnabled];
if (isOpen) {
NSLog(@"定位服務已經打開");
}else {
NSLog(@"定位服務未開啟");
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"你的定位服務未開啟" message:@"請到setting開啟定位服務" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return;
}
// 判斷應用是否獲取定位授權
NSInteger status = [CLLocationManager authorizationStatus];
switch (status) {
case 0:
NSLog(@"kCLAuthorizationStatusNotDetermined--沒有決定");// 用戶從未選擇過權限
break;
case 1:
NSLog(@"kCLAuthorizationStatusRestricted--沒有許可");// 無法使用定位服務,該狀態用戶無法改變
break;
case 2:
NSLog(@"kCLAuthorizationStatusDenied--禁止使用");// 用戶拒絕該應用使用定位服務,或是定位服務總開關處於關閉狀態
break;
case 3:
NSLog(@"kCLAuthorizationStatusAuthorizedAlways--始終允許");// 大致是用戶同意程序在任意時候使用地理位置
break;
case 4:
NSLog(@"kCLAuthorizationStatusAuthorizedWhenInUse--開啟允許");// 大致是用戶同意程序在可見時使用地理位置
break;
default:
break;
}
// 2.初始化定位管家的對象,注意需要設置為全局變量,因為我們需要一直持有定位管家的對象,局部變量使用後即被銷毀,在代理方法中,無法獲得該對象及其屬性,所以需要設置為全局變量
[self locationManager];
// 3.判斷定位服務授權狀態,設置定位權限:iOS8的新特性,可以實現代理方法獲取授權范圍。注意需要修改plist文件NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
// 判斷授權狀態,在授權未確定是獲取授權,一旦確定即無法在程序中修改,只能在settings中對app做授權設置
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {// 判斷系統版本 大於8.0以上的版本可以手動獲取授權
// 請求始終允許訪問,包括進入後台後 對應info設置:NSLocationAlwaysUsageDescription
// [self.locationManager requestAlwaysAuthorization];
// 請求當app打開時允許訪問 對應info設置:NSLocationWhenInUseUsageDescription
[self.locationManager requestWhenInUseAuthorization];
// 注意:修改plist文件
// NSLocationAlwaysUsageDescription---我想在後台還訪問您的位置
// NSLocationWhenInUseUsageDescription---我想在我的app開啟的時候使用您的位置,可以嗎?
}
}else if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"你沒有給該應用的定位服務授權" message:@"請到setting設置定位服務授權" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
return;// 沒有給予授權
}
// 4.設置定位服務的屬性。
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
// 設置定位刷新距離,可以直接是由上面的參數指定
[self.locationManager setDistanceFilter:100];//移動距離大於distanceFilter就會定位,否則不會,避免頻繁的定位,消耗電量
// 5.開啟定位,實現代理方法,獲取定位信息
[self.locationManager startUpdatingLocation];
// [self.locationManager stopUpdatingLocation];// 獲取定位數據後調用
// 開始追蹤導航方向,
[self.locationManager startUpdatingHeading];
// [self.locationManager stopUpdatingHeading];// 停止追蹤導航方向
// 開啟區域追蹤,需要傳入一個追蹤區域
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(39.0, 116.0);
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:1000 identifier:@"找到你了"];
[self.locationManager startMonitoringForRegion:region];
}
// 懶加載定位管家
- (CLLocationManager *)locationManager {
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;// 設置定位服務的代理對象
}
return _locationManager;
}
#pragma mark CLLocationManagerDelegate
/**
* 方法說明:當用戶許可狀態發生改變時,調用該方法
*
* @param status:用戶的許可狀態
*
* @return
*/
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
NSLog(@"認證狀態改變 status:%d",status);
}
/**
* 方法說明:執行定位後調用該方法
*
* @param
*
* @return
*/
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations {
// 停止定位(省電措施:只要不想用定位服務,或者獲取定位信息後,就馬上停止定位服務)
[self.locationManager stopUpdatingLocation];
// 1.取出位置對象(數組中可能會有多個位置對象,取出第一個是最精確的)
CLLocation *loc = [locations firstObject];
// 2.取出經緯度 coordinate:位置坐標 course:方向
CLLocationCoordinate2D coordinate = loc.coordinate;
CLLocationDegrees longitude = coordinate.longitude;
CLLocationDegrees latitude = coordinate.latitude;
// 3.CLLocations的常用屬性和方法
/*
horizontalAccuracy,用來得到水平上的精確度,它的大小就是定位精度的半徑,單位為米。獲得的不是用戶設置的精度 而是最終定位的精度,如果值為-1,則說明此定位不可信。
course 方向: 0 ~ 359.9 , 0 代表正北
speed 速度:m/s
獲取兩個位置之間的距離
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
*/
NSLog(@"經度:%f \n 緯度:%f", longitude, latitude);
NSLog(@"方向:%f \n 海拔:%f",loc.course, loc.altitude);
NSLog(@"水平定位精度%f 豎直定位精度%f",loc.horizontalAccuracy,loc.verticalAccuracy);
NSLog(@"速度:%f",loc.speed);
// 計算2個經緯度之間的直線距離
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:40 longitude:116];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:41 longitude:116];
// 計算2個經緯度之間的直線距離
CGFloat distance = [self countLineDistance:loc1 withLocation:loc2];
NSLog(@"%f",distance);
}
/**
* 計算2個經緯度之間的直線距離
*/
- (double)countLineDistance:(CLLocation *)loc1 withLocation:(CLLocation *)loc2
{
CLLocationDistance distance = [loc1 distanceFromLocation:loc2];
return distance;
}
/**
* 方法說明: 導航方向發生變化的時候執行此方法
*
* @param newHeading 方向: 0 ~ 359.9 , 0 代表正北
*
* @return
*/
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
NSLog(@"方向改變");
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(@"進入到該區域");
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{
NSLog(@"離開該區域");
}
@end
使用定位功能時,有幾點需要我們注意:
定位頻率和定位精度並不應當越精確越好,需要視實際情況而定,因為越精確越耗性能,也就越費電。 定位成功後會根據設置情況頻繁調用-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray )locations方法,使用完定位服務後如果不需要實時監控應該立即關閉定位服務以節省資源,所以我們需要在合適的時候停止定位。 -(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray )locations方法返回一組地理位置對象數組,每個元素一個CLLocation代表地理位置信息(包含經度、緯度、海報、行走速度等信息),之所以返回數組是因為有些時候一個位置點可能包含多個位置。除了提供位置跟蹤功能之外,在定位服務中還包含CLGeocoder類用於處理地理編碼和逆地理編碼功能。這個功能的實現主要是由CLGeocoder類提供的。CLGeocoder最主要的兩個方法就是
-(void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;// 根據給定的位置(通常是地名)確定地理坐標(經、緯度)。 -(void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler; // 根據地理坐標(經、緯度)確定位置信息(街道、門牌等)。在上面兩個方法的方法中,當地理編碼或是反編碼結束時,會回調CLGeocodeCompletionHandler,當中傳遞給我們兩個參數,一個NSArray,一個NSError。NSError是編碼或者反編碼錯誤的詳情,數組中則存放著我們地理編碼或是反地理編碼後獲取的地標信息,為什麼是數組呢,因為我們可能查詢到多個地標,比如“帝都”代表的地標就有多個。在CoreLocation框架中,地標是一個CLPlacemark的實例對象。下面我們先來看一下CLPlacemark的常用屬性:
示例代碼
#import "ViewController.h"
#import
@interface ViewController ()
@property (nonatomic, strong)CLGeocoder *geocoder;//地理編碼器
#pragma mark - 地理編碼 根據地區名稱查詢所在的經緯度坐標
@property (strong, nonatomic) IBOutlet UITextField *addressField;//輸入地區名稱
@property (strong, nonatomic) IBOutlet UILabel *longitudeLabel;//顯示地區的經度
@property (strong, nonatomic) IBOutlet UILabel *latitudeLabel;//顯示地區的緯度
@property (strong, nonatomic) IBOutlet UILabel *detailAddressLabel;//顯示地區的詳細信息
#pragma mark - 反地理編碼 根據經緯度獲取對應的地區名
@property (strong, nonatomic) IBOutlet UITextField *reverseLongtitudeField;//查詢地區的經度
@property (strong, nonatomic) IBOutlet UITextField *reverseLatitudeField;//查詢地區的緯度
@property (strong, nonatomic) IBOutlet UILabel *reverseDetailAddressLabel;//查詢的地區詳細名稱
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化地理編碼器
if (!_geocoder) {
_geocoder = [[CLGeocoder alloc] init];
}
}
// 地理編碼
- (IBAction)geocoder:(id)sender {
//獲取輸入的城市名稱
NSString *address = self.addressField.text;
if (address.length == 0) return;
[self getCoordinateByAddress:address withBlock:^(CLPlacemark *pm) {
// 設置經緯度
self.latitudeLabel.text = [NSString stringWithFormat:@"%.2f", pm.location.coordinate.latitude];
self.longitudeLabel.text = [NSString stringWithFormat:@"%.2f", pm.location.coordinate.longitude];
// 設置具體地址
self.detailAddressLabel.text = pm.name;
} withfaild:^(NSError *error) {
self.detailAddressLabel.text = @"你找的地址可能不存在,請重新輸入";
self.addressField.text = @"";
}];
}
- (void)getCoordinateByAddress:(NSString *)address withBlock:(void(^)(CLPlacemark *pm))block withfaild:(void(^)( NSError *error))faild{
[_geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) { // 有錯誤(地址亂輸入)
faild(error);
} else { // 編碼成功
// 取出最前面的地址(數組中可能存在過河產訊到的地址)
CLPlacemark *pm = [placemarks firstObject];
// 回調數據
block (pm);
NSLog(@"總共找到%ld個地址", placemarks.count);
//遍歷數組,獲取所有查找到的城市
for (CLPlacemark *pm in placemarks) {
NSLog(@"-----地址開始----");
// 枚舉編譯出得所有的地理信息
[pm.addressDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"%@:%@", key, obj);
}];
NSLog(@"-----地址結束----");
}
}
}];
}
- (IBAction)reverseGeocoder:(id)sender {
// 1.包裝位置,將輸入的字符串設置為地理坐標
CLLocationDegrees latitude = [self.reverseLatitudeField.text doubleValue];
CLLocationDegrees longitude = [self.reverseLongtitudeField.text doubleValue];
CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[self getAddressByLocation:loc withBlock:^(CLPlacemark *pm) {
// 設置具體地址
self.reverseDetailAddressLabel.text = [NSString stringWithFormat:@"%@ %@ %@ %@ %@",pm.country,pm.locality,pm.subLocality,pm.thoroughfare,pm.subThoroughfare];
} withFaild:^(NSError *error) {
self.reverseDetailAddressLabel.text = @"你找的地址可能只在火星有!!!";
}];
}
- (void)getAddressByLocation:(CLLocation *)loc withBlock:(void(^)(CLPlacemark *pm))block withFaild:(void(^)(NSError *error))faild {
// 2.反地理編碼
[_geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) { // 有錯誤(地址亂輸入)
faild (error);
} else { // 編碼成功
// 取出最前面的地址
CLPlacemark *pm = [placemarks firstObject];
block (pm);
NSLog(@"總共找到%ld個地址", placemarks.count);
for (CLPlacemark *pm in placemarks) {
NSLog(@"-----地址開始----");
// 獲取所有的地標信息
[pm.addressDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"%@:%@", key, obj);
}];
NSLog(@"-----地址結束----");
}
}
}];
}
@end
在前幾篇文章中《Android 采用get方式提交數據到服務器》《Android 采用post方式提交數據到服務器》《Android 采用HttpClient提交數據到服
本想自己寫一個的,但是看到這篇之後,我想還是轉過來吧,實在是非常的詳細:在Android系統中,發一個狀態欄通知還是很方便的。下面我們就來看一下,怎麼發送狀態欄通知,狀態
問題背景:在做圖表展示的時候,ListView可以上下左右滑動,但最左邊一列在向右滑動時,保持不變,表頭在向下滑動時保持不變。有用兩個ListView實現的,但測試過,好
如果listitem裡面包括button或者checkbox等控件,默認情況下listitem會失去焦點,導致無法響應item的事件,最常用的解決辦法是在listitem