iOS定位相关功能开发经验总结

1. Core Location 与 MapKit

1.1 Core Location —— 用于获取设备当前地理位置信息与朝向

  • 初始化与获取授权
    后台定位需要在Info.plist中添加对应键值

    Info.plist

    工厂环境下在组件配置中写入

    <ios>
      <meta-datas>
        <meta-data xpath="//plist/dict" key="UIBackgroundModes" type="array" file="/ComponentAppBase/Info.plist">
          <![CDATA[
             <string>location</string>
          ]]>
        </meta-data>
      </meta-datas>
    </ios>
    

    初始化

    self.locationManager = [[CLLocationManager] alloc] init];
    self.locationManager.delegate = self;
    self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
    self.locationManager.distanceFilter = 10;
    self.locationManager.pausesLocationUpdatesAutomatically = NO;
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
        self.locationManager.allowsBackgroundLocationUpdates = YES;
    }
    

    CLLocationManager初始化后,以及app的授权状态改变时,locationManager: didChangeAuthorizationStatus:会被调用。

    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
            if (status == kCLAuthorizationStatusNotDetermined) {
                [self.locationManager requestAlwaysAuthorization];
            } else if (status != kCLAuthorizationStatusRestricted && status != kCLAuthorizationStatusDenied) {
                [self.locationManager startUpdatingLocation]; //开始获取GPS位置
                if ([CLLocationManager headingAvailable]) {
                    [self.locationManager startUpdatingHeading]; //开始获取设备朝向
                }
            } else {
                //无授权处理
            }
        }
    }
    
    typedef NS_ENUM(int, CLAuthorizationStatus) {
      kCLAuthorizationStatusNotDetermined = 0,
      kCLAuthorizationStatusRestricted,
      kCLAuthorizationStatusDenied,
      kCLAuthorizationStatusAuthorizedAlways,
      kCLAuthorizationStatusAuthorizedWhenInUse,
      kCLAuthorizationStatusAuthorized //只在macOS下使用
    };
    


  • 获得位置与朝向 CLLocationManagerDelegate
    - (void)locationManager:(CLLocationManager *)manager 
          didUpdateLocations:(NSArray<CLLocation *> *)locations {
    }
    
    - (void)locationManager:(CLLocationManager *)manager
          didUpdateHeading:(CLHeading *)newHeading {
    }
    
    这里获得的经纬度属于WGS84坐标系,而中国使用的是加密后的GCJ02坐标系,需要进行换算,具体方法参见这里

  • Region Monitoring 监测用户进入或离开特定地理区域
    在iOS中,系统自动对用户的区域变化进行监测,在用户进入或离开我们定义的区域时,相应代理方法会执行。如果事件发生时我们的app并没有运行,系统会在后台唤醒我们的app。可以通过launchOptions字典中的UIApplicationLaunchOptionsKey来判断我们的app是否是由Region Monitoring唤醒的。同一个app最多只能同时监测20个region。


    使用步骤
    1. 创建 CLCircularRegion 对像,用于确定监测的区域。只能为圆形,官方不支持多边形区域。
    2. 注册区域,iOS会持续监测区域,直到我们的代码中停止区域监测,或者手机重启。
      [self.locationManager startMonitoringForRegion:region];
      
    3. 实现 locationManager:didEnterRegion:locationManager:didExitRegion: 代理方法
    4. AppDelegate.m文件的application:didFinishLaunchingWithOptions:函数中判断程序是否由Region Monitoring唤醒,如果是,进行处理。

  • Geocoding 坐标与实际街道名之间的转换
    • 前一个请求未完成时,后续请求会失败。

    • 苹果服务器会对每个app的请求频率做限制,超过限制之后的请求会失败。

    • 使用的坐标为地图提供的坐标系

    • 坐标转街道

      self.geocoder = [CLGeocoder new];
      [self.geocoder reverseGeocodeLocation:location 
                        completionHandler:^(NSArray *placemarks, NSError *error) {
      }];
      
    系统进行异步网络请求,completionHandler会在主线程执行。
    • 街道转坐标
    - (void)geocodeAddressDictionary:(NSDictionary *)addressDictionary completionHandler:(CLGeocodeCompletionHandler)completionHandler;
    - (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
    - (void)geocodeAddressString:(NSString *)addressString inRegion:(nullable CLRegion *)region completionHandler:(CLGeocodeCompletionHandler)completionHandler;
    


1.2 MapKit —— 在app中显示地图,在地图上显示景点,添加标记(Annotation),获取导航路径等

  • MKMapViewUIView的子类)
    • 不需要定位用户位置时

      MKMapView *mapView = [MKMapView new];
      mapView.delegate = self;
      [self.view addSubview:mapView];
      [mapView mas_makeConstraints:^(MASConstraintMaker *make) {
          make.edges.equalTo(self.view);
      }];
      
    • 需要定位用户位置时

      1. 创建CLLocationManager申请权限
      2. 在地图上显示用户位置
        self.mapView.showsUserLocation = YES;
        
        也可以通过实现MKMapViewDelegate的代理方法获取用户坐标
        - (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
            static BOOL didBeginInitialize = NO;
            if (!didBeginInitialize) {
                didBeginInitialize = YES;
                MKCoordinateSpan span = MKCoordinateSpanMake(0.02, 0.02);
                MKCoordinateRegion region = MKCoordinateRegionMake(userLocation.location.coordinate, span);
                [self.mapView setRegion:region animated:NO];
            }
        }
        
  • MKAnnotationMKAnnotationView

    • 什么是Annotation


    • 添加方法

      MKPointAnnotation *annotation = [MKPointAnnotation new];
      annotation.title = @"title";
      annotation.coordinate = CLLocationCoordinate2DMake(latitude,longitude);
      [self.mapView addAnnotation:annotation];
      
      点击后的气泡中显示title
    • 如何定制

      - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
          if (annotation == self.mapView.userLocation) {
            //用户位置view
            MKAnnotationView *annotationView = [[MKAnnotationView  alloc] initWithAnnotation:annotation reuseIdentifier:nil];
            // ...
            return annotationView;
          } else { 
            //其他Annotation
          }
      }
      

      返回nil则会使用默认样式


      自定义的annotationView
    • 修改已生成的annotationView,以及动效编写

      • annotationannotationView一一对应,修改annotation的属性值,对应的annotationView会有相应变化。
      • 系统会自动缓存已创建的annotationView,手动调用
        [self.mapView viewForAnnotation:annotation]时,如果annotation对应的annotationView已创建过,则不会创建新的annotationView,而是返回已缓存的。
      [self.mapView layoutIfNeeded];
      MKAnnotationView *annotationView = [self.mapView viewForAnnotation:annotation];
      // ...相关视图处理代码
      [UIView animateWithDuration:0.3 animations:^{
          [self.mapView layoutIfNeeded];
      }];
      


  • 区域标记 MKOverlayMKOverlayRenderer

    圆形overlay

    多边形overlay

    圆形区域

    MKCircle *circle = [MKCircle circleWithCenterCoordinate:coordinate radius:1000];
    [self.mapView addOverlay:circle level:MKOverlayLevelAboveLabels];
    

    多边形区域

    CLLocationCoordinate2D *mapPointArray = (CLLocationCoordinate2D *)malloc(sizeof(CLLocationCoordinate2D) * count);
    for (NSInteger i = 0; i < N; i ++) {
        mapPointArray[i] = CLLocationCoordinate2DMake(latitude, longitude);
    }
    MKPolygon *polygon = [MKPolygon polygonWithCoordinates:mapPointArray count:count];
    [self.mapView addOverlay:polygon level:MKOverlayLevelAboveLabels];
    // 记得释放mapPointArray
    

    区域层级ENUM

    typedef NS_ENUM(NSInteger, MKOverlayLevel) {
      MKOverlayLevelAboveRoads = 0, // note that labels include shields and point of interest icons.
      MKOverlayLevelAboveLabels
    } NS_ENUM_AVAILABLE(10_9, 7_0) __TVOS_AVAILABLE(9_2) __WATCHOS_PROHIBITED;
    

    代理中返回对应视图

    - (MKOverlayRenderer *)mapView:(MKMapView *)map rendererForOverlay:(nonnull id<MKOverlay>)overlay {
        if ([overlay isKindOfClass:[MKCircle class]]) {
            MKCircleRenderer *circleRenderer = [[MKCircleRenderer alloc] initWithOverlay:overlay];
            circleRenderer.strokeColor = [[UIColor apf_colorWithHexString:@"ff9d2a"] colorWithAlphaComponent:0.3];
            circleRenderer.lineWidth = 3;
            circleRenderer.fillColor = [[LBSSLHelper colorWithKey:@"color_19"] colorWithAlphaComponent:0.25];
            return circleRenderer;
          
        } else if ([overlay isKindOfClass:[MKPolygon class]]) {
            MKPolygonRenderer *polygonRenderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
            polygonRenderer.strokeColor = [[UIColor apf_colorWithHexString:@"ff9d2a"] colorWithAlphaComponent:0.3];
            polygonRenderer.lineWidth = 3;
            polygonRenderer.fillColor = [[LBSSLHelper colorWithKey:@"color_19"] colorWithAlphaComponent:0.25];
            return polygonRenderer;
        }
    }
    
  • 两点间路线 MKDirectionsRequest

    MKDirectionsRequest *directionsRequest = [MKDirectionsRequest new];
    [directionsRequest setTransportType:MKDirectionsTransportTypeWalking];
    [directionsRequest setSource:[[MKMapItem alloc] initWithPlacemark:originPlacemark]];
    [directionsRequest setDestination:[[MKMapItem alloc] initWithPlacemark:destinationPlacemark]];
    [directionsRequest setRequestsAlternateRoutes:NO];
    MKDirections *direction = [[MKDirections alloc] initWithRequest:directionsRequest];
    
    [direction calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
        if (!error) {
            //response中还有换乘建议、预计耗时等其他信息
            MKRoute *route = [response.routes firstObject];
            if (route) {
                [self.mapView addOverlay:route.polyline level:MKOverlayLevelAboveLabels];
            }
        }
    }];
    
    - (MKOverlayRenderer *)mapView:(MKMapView *)map rendererForOverlay:(nonnull id<MKOverlay>)overlay {
        if ([overlay isKindOfClass:[MKPolyline class]]) {
            MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
            polylineRenderer.lineWidth = 4;
            polylineRenderer.strokeColor = [LBSSLHelper colorWithKey:@"color_14"];
            return polylineRenderer;
        }
    }
    

2. 遇到的问题

  1. 单元测试报错



    解决方法:设置单元测试的环境变量,跳过属性设置。



  2. 工厂打包时的权限问题
    解决方法:使用子组件
    <property type="bool" name="showLocationShare" desc_true="需要位置分享" desc_false="不需要位置分享" displayName="是否需要位置分享" value="false">
      <dependency>
        <component namespace="com.nd.social" name="lbs-share-location"/>
      </dependency>
    </property>
    
  3. 请求后台运行权限上架被拒
    解决方法:与苹果审核人员沟通。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容