你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> CoreLocation 定位

CoreLocation 定位

編輯:IOS開發綜合

前言
本章會使用OC和Swift分別進行實現,需要了解Swift的小伙伴可以翻一下之前的博文

LBS和SoloMo(索羅門)

LBS:基於位置的服務,根據定位展示周邊美食、景點等信息(全稱:Location Bassed Service) SoloMo:將位置社交、本地、移動化(全稱:Soclal Local Moblle)
社交化:在APP內加入一些社交元素,進行位置分享等 本地化:基於LBS周邊的搜索等服務 移動化:基於3G\4G網絡在移動APP上的服務

CoreLocation介紹

定位已經可以說是現在APP的主流,沒有定位功能的APP都不好意思和大家見面,作為APP的重要組成部分,其實使用也非常簡單,本章就蘋果的CoreLocation框架進行簡單分析和使用 CoreLocation主要功能
地理定位:獲取用戶所以在區域,得到相應的經緯度或者海拔等一些地理信息 地理編碼:根據詳細的地址轉換為經緯度信息 反地理編碼:根據經緯度信息轉換成具體地址 區域監聽:指定一個區域,當用戶進入或者離開這個區域,我們都可以監聽到對應信息 一般MapKit和一起使用,因為Mapkit就是基於CoreLocation進行開發的,所以MapKit能進行定位也能展示地圖,以後會就MapKit進行詳解

distanceFilter(距離過濾)和 desiredAccuracy(定位精確度)屬性

distanceFilter(距離過濾):最新位置距上次位置之間距離大於這個值,就會告訴通過代理告訴外界

默認距離KCLDistanceFilterNone (值為-1,因為小於0,所以會一直打印) 單位:米

desiredAccuracy(定位精確度):定位精確度越高,定位時間就越長,也就越耗電

kCLLocationAccuracyBestForNavigation // 最適合導航 kCLLocationAccuracyBest // 最好的 kCLLocationAccuracyNearestTenMeters; // 附近10米 kCLLocationAccuracyHundredMeters; // 附近100米 kCLLocationAccuracyKilometer; // 附近1000米 kCLLocationAccuracyThreeKilometers; // 附近3000米

iOS8之前定位

在XCode5之前我們需要用到的框架修需要手動導入(這邊使用的是XCode7.3,有沖突的請進行相應調整) CoreLocation框架的主頭文件#import
// 為了全局只使用一個位置管理者,我們先對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()
    }

startMonitoringSignificantLocationChanges(重大位置改變監聽)

當位置發生較大變化後會調用這個服務(基於基站定位,所以必須要有電話模塊)
優勢:當APP被完全關閉後,也可以接收到位置通知,並且讓APP進入後台處理,耗電量小 劣勢:定位精度相對於標准定位服務較低,更新的頻率根據當前位置附近的基站密度決定

後台繼續定位

如果想要在後台繼續進行定位,需要打開後台的定位模式

開啟後台定位模式.gifvc+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()
    }
}

定位適配

一般我們適配版本都會判斷當前設備的版本,然後再進行相應的適配操作,這邊就介紹另一種比較簡單的適配方式
通過respondsToSelector:方法來判斷方法是否可響應,可以的話再執行

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定位變化
前台定位於iOS8無變化
後台定位
方法一:在前台定位授權基礎上,勾選後台模式location updates之後,需要額外設置屬性allowsBackgroundLocationUpdates = YES 方法二:直接請求前後台定位授權,設置屬性allowsBackgroundLocationUpdates = YES,開啟後台模式

定位服務未開啟或者被用戶真正拒絕情況下的情況處理

iOS8之前,需要將開啟授權的截圖展示給用戶,讓用戶根據截圖去開啟授權 iOS8之後,會自動彈出設置窗口,讓用戶選擇是否需要開啟授權
iOS開始我們可以根據URL直接跳轉到相應的設置界面

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)")
}

區域監聽

區域監聽就是根據需求指定一塊區域,當用戶持設備進入或離開指定區域,我們都可以監聽到 iOS8開始,想要做區域監聽,必須請求位置授權(因為區域監聽原理就是獲取用戶位置,然後判斷位置是否在設定的區域內,涉及到用戶隱私)

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("在區域外")
        }
    }

注意:
必須請求用戶定位授權 使用前先判斷區域監聽是否可用 判斷區域半徑是否大於最大監聽區域,如果大於最大監聽區域范圍,則無法監聽成功

地理編碼和反地理編碼

地理編碼:指根據地質關鍵字,將其轉換成對應的經緯度等信息 反地理編碼:指根據經緯度信息,將其轉換成對應的省市區等信息 CLPlacemark(地表對象)
location:CLLocation類型,位置對象的信息,包含經緯度,海拔等 region:CLRegion類型,地表對象對應區域 addressDictionary:NSDictionary類型,存放省市,街道等信息 name:NSString類型,地址全稱 thoroughfare:NSString類型,街道名稱 locality:NSString類型,城市名稱 administrativeArea:NSString類型,省名稱 country:NSString類型,國家名稱 注意
必須聯網 有時候反地理編碼時會找不到對應信息,需要嘗試更換經緯度

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)")
        }

    }

 

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved