美文网首页
iOS定位相关功能开发经验总结

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

作者: Dungeon | 来源:发表于2017-08-23 14:41 被阅读0次

    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. 请求后台运行权限上架被拒
      解决方法:与苹果审核人员沟通。

    相关文章

      网友评论

          本文标题:iOS定位相关功能开发经验总结

          本文链接:https://www.haomeiwen.com/subject/wlchdxtx.html