Map

地图

  • CoreLocation :用于地理定位,地理编码,区域监听等(着重功能实现)
  • MapKit :用于地图展示,例如大头针,路线、覆盖层展示等(着重界面展示)
2个热门专业术语
  • LBS :Location Based Service
  • SoLoMo :Social Local Mobile(索罗门)

Core Location

  • CLLocationManager 定位

    • 需要在info.plist加入key“Privacy - Location Usage Description”,和对应value的描述
    • 初始化设置
    // 创建位置管理者
          _manager = [[CLLocationManager alloc] init];
    
          // 设置代理
          self.manager.delegate = self;
    
          // 每隔多少米定位一次
          _manager.distanceFilter = 100;
          /**
           kCLLocationAccuracyBestForNavigation // 最适合导航
           kCLLocationAccuracyBest; // 最好的
           kCLLocationAccuracyNearestTenMeters; // 10m
           kCLLocationAccuracyHundredMeters; // 100m
           kCLLocationAccuracyKilometer; // 1000m
           kCLLocationAccuracyThreeKilometers; // 3000m
           */
          // 精确度越高, 越耗电, 定位时间越长
          self.manager.desiredAccuracy = kCLLocationAccuracyBest;
          }
    
  • 版本适配
        /** -------iOS8.0+定位适配-------- */
        if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0 ) {
            // 前台定位授权(默认情况下,不可以在后台获取位置, 勾选后台模式 location update, 但是 会出现蓝条)
            //            [_lM requestWhenInUseAuthorization];


            // 前后台定位授权(请求永久授权)
            // +authorizationStatus != kCLAuthorizationStatusNotDetermined
            // 这个方法不会有效
            // 当前的授权状态为前台授权时,此方法也会有效
            [_manager requestAlwaysAuthorization];
        }

        // 允许后台获取用户位置(iOS9.0)
        if([[UIDevice currentDevice].systemVersion floatValue] >= 9.0)
        {
            // 一定要勾选后台模式 location updates
            _manager.allowsBackgroundLocationUpdates = YES;
  • 开始更新定位
    // 需要时,勾选后台模式 location updates
    [self.manager startUpdatingLocation];
  • 遵守代理协议CLLocationManagerDelegate,实现回调方法
/** 更新到位置后调用 */
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    [self.manager stopUpdatingLocation];
}

/** 授权状态发生改变时调用 */
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    switch (status) {
        case kCLAuthorizationStatusNotDetermined:
            NSLog(@"用户未决定");
            break;
        case kCLAuthorizationStatusRestricted:
            NSLog(@"访问受限制");
            break;
        case kCLAuthorizationStatusDenied:
            // 定位是否可用(是否支持定位或者定位是否开启)
            if ([CLLocationManager locationServicesEnabled]) {

                NSLog(@"定位开启,但被拒");
            }else
            {
                NSLog(@"定位关闭");
            }

            break;
        case kCLAuthorizationStatusAuthorizedAlways:
            NSLog(@"获取前后台定位");
            break;
        case kCLAuthorizationStatusAuthorizedWhenInUse:
            NSLog(@"获取前后台定位");
            break;
        default:
            break;
    }
}

/** 定位失败 */
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
  • 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个位置之间的距离
  • 场景演示:打印当前用户的行走方向,偏离角度以及对应的行走距离
 // 老位置
 static CLLocation *_oldL;

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    CLLocation *location = [locations lastObject];

    // 1. 获取方向偏向
    NSString *angleStr = nil;

    switch ((int)location.course / 90) {
        case 0:
            angleStr = @"北偏东";
            break;
        case 1:
            angleStr = @"东偏南";
            break;
        case 2:
            angleStr = @"南偏西";
            break;
        case 3:
            angleStr = @"西偏北";
            break;

        default:
            angleStr = @"跑沟里去了!!";
            break;
    }

    // 2. 偏向角度
    NSInteger angle = 0;
    angle = (int)location.course % 90;

    // 代表正方向
    if (angle == 0) {
        NSRange range = NSMakeRange(0, 1);
        angleStr = [NSString stringWithFormat:@"正%@", [angleStr substringWithRange:range]];
    }

    // 3.移动多少米
    double distance = 0;
    if(_oldL)
    {
        distance = [location distanceFromLocation:_oldL];
    }
    _oldL = location;

    // 4. 拼串 打印
    // 例如:”北偏东 30度  方向,移动了8米”
    NSString *noticeStr = [NSString stringWithFormat:@"%@%zd方向, 移动了%f米", angleStr, angle, distance];

    NSLog(@"%@", noticeStr);
}

指南针的实现

CLLocationManager manager = [[CLLocationManager alloc] init];

_manager.delegate = self;

// 采样间隔(度)
_manager.headingFilter = 2;

// 开始监听手机朝向改变
[self.manager startUpdatingHeading];

/**
 *  获取到手机朝向时调用
 *  @param manager    位置管理者
 *  @param newHeading 朝向对象
 */
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
 /**
  *  CLHeading
  *  magneticHeading : 磁北角度
  *  trueHeading : 真北角度
  */
    CGFloat angle = newHeading.magneticHeading / 180 * M_PI;
    [UIView animateWithDuration:0.2 animations:^{
        self.imageView.transform = CGAffineTransformMakeRotation(-angle);

    }];
}

区域监听

CLLocationManager *manager = [[CLLocationManager alloc] init];
        _manager.delegate = self;

        if([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)
        {
            [_manager requestAlwaysAuthorization];
        }

// 开启区域监听 这个方法iOS 7 已过期
CLLocationCoordinate2D center = {31.23, 133.451};
    CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:1000 identifier:@"sinalme"];
    // 请求区域状态
    [self.lM requestStateForRegion: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
{
    NSLog(@"%zd", state);
}

(反)地理编码

  • 地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
  • 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;
城市
  • CLGeocoder
NSString *address = self.textView.text;

    if (address.length == 0) {
        return;
    }

    [self.geocoder geocodeAddressString:@"中山" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        if (error == nil) {

            [placemarks enumerateObjectsUsingBlock:^(CLPlacemark * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

                self.textView.text = obj.name;
                self.LontextField.text = @(obj.location.coordinate.longitude).stringValue;
                self.latTextField.text = @(obj.location.coordinate.latitude).stringValue;
            }];

        }else
        {
            NSLog(@"mistake-%@",error.localizedDescription);
        }

    }];
  • 反地理编码:根据给定的经纬度,获得具体的位置信息
    double lon = self.LontextField.text.doubleValue;
    double lat = self.latTextField.text.doubleValue;

    CLLocation *location = [[CLLocation alloc] initWithLatitude:lat longitude:lon];

    [self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        if (!error) {

            [placemarks enumerateObjectsUsingBlock:^(CLPlacemark * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

                self.textView.text = obj.name;
                self.LontextField.text = @(obj.location.coordinate.longitude).stringValue;
                self.latTextField.text = @(obj.location.coordinate.latitude).stringValue;
            }];

        }else
        {
            NSLog(@"%@",error.localizedDescription);
        }

    }];

第三方框架 LocationManager

  • iOS 8
    • NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription
    • in your app's Info.plist 添加描述
INTULocationManager *locMgr = [INTULocationManager sharedInstance];
    [locMgr requestLocationWithDesiredAccuracy:INTULocationAccuracyCity  timeout:10.0 delayUntilAuthorized:YES  block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {

        if (status == INTULocationStatusSuccess) {

            NSLog(@"%@",currentLocation);
        }
        else if (status == INTULocationStatusTimedOut) {
            NSLog(@"超时");
        }
        else {

            NSLog(@"未知");
        }
    }];

MapKit

  • 添加 MapKit.framework
  • 导入头文件 MapKit/MapKit.h
  • 添加 MKMapView
  • 创建 CLLocationManager 对象
  • 判断是否授权
 if ([_lm respondsToSelector:@selector(requestAlwaysAuthorization)]) {
      [_lm requestAlwaysAuthorization];
}
  • 设置mapView相关属性
 self.mapView.showsUserLocation = YES;
    self.mapView.userTrackingMode = MKUserTrackingModeFollowWithHeading;
  • 遵守 MKMapViewDelegate 代理协议,实现协议方法
/**
 *  更新到位置
 *
 *  @param mapView      地图
 *  @param userLocation 位置对象
 */
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
    /** MKUserLocation (大头针模型) */
    userLocation.title = @"";
    userLocation.subtitle = @"";

    // 设置地图显示中心
   [self.mapView setCenterCoordinate:userLocation.location.coordinate animated:YES];

    // 设置地图显示区域
    MKCoordinateSpan span = MKCoordinateSpanMake(0.051109, 0.034153);
    MKCoordinateRegion region = MKCoordinateRegionMake(userLocation.location.coordinate, span);
    [self.mapView setRegion:region animated:YES];
}

大头针的实现

  • 加MapKit.framework,导入MapKit/MapKit.h,info中添加key Privacy - Location Always Usage Description
  • 创建mapView,获取屏幕触摸点,转换成经纬度
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    CGPoint point = [[touches anyObject] locationInView:self.mapView];

    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];

    // 添加大头针
    [self addAnnoWithCoordinate:coordinate];
}
  • 自定义大头针
#import <MapKit/MapKit.h>

@interface WZAnno : NSObject <MKAnnotation>

@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy, nullable) NSString *title;
@property (nonatomic, copy, nullable) NSString *subtitle;
@property (nonatomic,assign) NSInteger type;
@end
  • 添加大头针
- (void)addAnnoWithCoordinate:(CLLocationCoordinate2D)coordinate
{
    // 创建标签
    __block WZAnno *anno = [[WZAnno alloc] init];
    anno.title = @"中国山东";
    anno.subtitle = @"找蓝翔";
    anno.coordinate = coordinate;
    anno.type = arc4random_uniform(5);
    // 给mapView添加标签
    [self.mapView addAnnotation:anno];
    // 获取位置
    CLLocation *location = [[CLLocation alloc] initWithLatitude:anno.coordinate.latitude longitude:anno.coordinate.longitude];
    // 反地理编码
    [self.geocoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        CLPlacemark *placemark = [placemarks firstObject];
        anno.title = placemark.locality;
        anno.subtitle = placemark.thoroughfare;

    }];
}
  • 设置mapView代理,遵守MKMapViewDelegate协议,实现回调方法,设置大头针弹出的泡泡view相关属性
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
    // 循环利用大头针弹出的泡泡view
    static NSString *ID = @"annoView"; // 标识符
    MKAnnotationView *annoV = [mapView dequeueReusableAnnotationViewWithIdentifier:ID];
    if (annoV == nil) {
        annoV = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ID];
    }
    // 防止循环利用或者annoV有值,需要重新赋值
    annoV.annotation = annotation;
    // 拖动
    annoV.draggable = YES;
    // 是否弹出标注
    annoV.canShowCallout = YES;

    // 设置不同类型的大头针图片
    WZAnno *anno = (WZAnno *)annotation;
    NSString *iamgeName = [NSString stringWithFormat:@"category_%zd",anno.type];
    annoV.image = [UIImage imageNamed:iamgeName];

    // 泡泡view左右添加imageView
    UIImageView *lImageV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
    lImageV.image = [UIImage imageNamed:@"htl"];
    annoV.leftCalloutAccessoryView = lImageV;

    UIImageView *rImageV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
    rImageV.image = [UIImage imageNamed:@"eason"];
    annoV.rightCalloutAccessoryView = rImageV;

    // 泡泡view中间添加imageView
    annoV.detailCalloutAccessoryView = [[UISwitch alloc] init];

    return annoV;
}
/** 选中大头针 */
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view;
/** 取消选中大头针 */
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view;

地图快照

    // 地图快照的设置对象
    MKMapSnapshotOptions *option = [[MKMapSnapshotOptions alloc] init];
    option.size = CGSizeMake(1000, 2000);
    option.scale = [UIScreen mainScreen].scale;
    option.region = self.mapView.region;
    option.showsBuildings = YES;

    // 创建地图快照对象
    MKMapSnapshotter *snap = [[MKMapSnapshotter alloc] initWithOptions:option];

    // 快照回调
    [snap startWithCompletionHandler:^(MKMapSnapshot * _Nullable snapshot, NSError * _Nullable error) {

        if (error == nil) {

            UIImage *image = snapshot.image;
            NSData *data = UIImagePNGRepresentation(image);

            [data writeToFile:@"users/apple/desktop/snapImage2.png" atomically:YES];
        }
    }];

导航

        // 通过地理编码拿到起点和终点
       [self.geocoder geocodeAddressString:@"中山" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

        CLPlacemark *beginP = [placemarks firstObject];

        [self.geocoder geocodeAddressString:@"上饶" completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {

            CLPlacemark *endP = [placemarks firstObject];

            [self startNaviWithBeganPlacemark:beginP endPlacemark:endP];
        }];

    }];


    // 起点
    MKPlacemark *bePlacemark = [[MKPlacemark alloc] initWithPlacemark:beginP];
    MKMapItem *beginItem = [[MKMapItem alloc] initWithPlacemark:bePlacemark];

    // 终点
    MKPlacemark *endPlacemark = [[MKPlacemark alloc] initWithPlacemark:endP];
    MKMapItem *endItem = [[MKMapItem alloc] initWithPlacemark:endPlacemark];

    NSArray *items = @[beginItem,endItem];

    NSDictionary *dict = @{
                           MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving,

                           MKLaunchOptionsMapTypeKey : @(MKMapTypeHybrid),
                           MKLaunchOptionsShowsTrafficKey : @(YES)
                           };

    // 开始导航
    [MKMapItem openMapsWithItems:items launchOptions:dict];
获取导航信息、绘制导航路线

    // 给起点和终点添加一个圆形覆盖层
    MKCircle *beginCircle = [MKCircle circleWithCenterCoordinate:beginP.location.coordinate radius:100000];
    [self.mapView addOverlay:beginCircle];

    MKCircle *endCircle = [MKCircle circleWithCenterCoordinate:endP.location.coordinate radius:100000];
    [self.mapView addOverlay:endCircle];

    // 创建请求
    MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];

    // 添加起点和终点
    MKPlacemark *bePlacemark = [[MKPlacemark alloc] initWithPlacemark:beginP];
    MKMapItem *sourceItem = [[MKMapItem alloc] initWithPlacemark:bePlacemark];
    request.source = sourceItem;

    MKPlacemark *endPlacemark = [[MKPlacemark alloc] initWithPlacemark:endP];
    MKMapItem *destinationItem = [[MKMapItem alloc] initWithPlacemark:endPlacemark];
    request.destination = destinationItem;

    MKDirections *direction = [[MKDirections alloc] initWithRequest:request];

    // 遍历导航信息
    [direction calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {
        /**
         *  MKDirectionsResponse
         routes : 路线数组MKRoute

         */
        /**
         *  MKRoute
         name : 路线名称
         distance : 距离
         expectedTravelTime : 预期时间
         polyline : 折线(数据模型)
         steps
         */
        /**
         *  steps <MKRouteStep *>
         instructions : 行走提示
         */

        [response.routes enumerateObjectsUsingBlock:^(MKRoute * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

            NSLog(@"%@ - %f - %f",obj.name,obj.expectedTravelTime,obj.distance);

            MKPolyline *polyline = obj.polyline;
            // 给路线添加一个覆盖层数据模型
            [self.mapView addOverlay:polyline];

            [obj.steps enumerateObjectsUsingBlock:^(MKRouteStep * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

                NSLog(@"%@",obj.instructions);
            }];
        }];

    }];
  • 实现mapView代理方法
/** 获取对应的图层渲染,返回渲染图层 */
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
    if ([overlay isKindOfClass:[MKCircle class]]) {

        MKCircleRenderer *render = [[MKCircleRenderer alloc] initWithOverlay:overlay];

        render.fillColor = [UIColor cyanColor];
        render.alpha = 0.5;

        return render;

    }

    if ([overlay isKindOfClass:[MKPolyline class]]) {

        MKPolylineRenderer *lineRender = [[MKPolylineRenderer alloc] initWithOverlay:overlay];

        lineRender.strokeColor = [UIColor redColor];
        lineRender.lineWidth = 10;

        return lineRender;
    }
    return nil;
}

results matching ""

    No results matching ""