iOS_系统自带地图圆形区域选择范围

2022-07-20 13:55:13 浏览数 (2)

思路: (由于项目需求, 我用的Masonry布局)

1.运用MKMapView展示地图

2.运用CLLocationManager获取用户定位

3.defauct: 聚焦到用户定位, (并时刻跟踪其位置)

4.实现长按聚焦, 搜索聚焦(地理编码), (不跟踪用户定位)

5.聚集操作:删除原理的大头针,在新经纬度添加大头针,并将地图移动到新的经纬度(反地理编码获得位置信息)

6.大头针定制: 

    (1)只赋值了原来的image, 

    (2)然后在上面铺了一层shadowView,

    (3)shadowView上有个按钮,添加了拖拽手势,实shadowView的放大缩小

    (4)并根据按钮的center和shaowView的center计算出两个间的距离

    (5)画虚线shapeLayer, label显示半径大小

7.点击右下角按钮, 返回用户定位, 并时刻跟踪

代码实现: (一共两个类 ViewController 和 WWAnnotationView)

//  ViewController.m

代码语言:javascript复制
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#import "WWAnnotationView.h"
#import <Masonry/Masonry.h>

// 状态栏高度
#define kStatusHeight ([UIApplication sharedApplication].statusBarFrame.origin.y   [UIApplication sharedApplication].statusBarFrame.size.height)
#define kDefault (100)

@interface ViewController () <CLLocationManagerDelegate, MKMapViewDelegate, WWWAnnotationViewDelegate, UISearchBarDelegate>
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) MKMapView *mapView;
@property (nonatomic, strong) WWAnnotationView *currentAnnotationView;
@property (nonatomic, strong) UILabel *addressLb;
@property (nonatomic, strong) MKCircle *circle;
@property (nonatomic, assign) CLLocationCoordinate2D userCoordinate;
@end

@implementation ViewController {
  UILabel *_radiusLb;
  BOOL _followUserLoc;  // 是否跟踪用户定位
}

- (void)viewDidLoad {
  [super viewDidLoad];
  _followUserLoc  = YES;
  [self setupView];
  
  if ([CLLocationManager locationServicesEnabled]) {
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    // 移动最大更新距离 (及移动距离超过此值, 就会受到回调)
    // 默认: kCLDistanceFilterNone 回调任何移动
    self.locationManager.distanceFilter = kCLDistanceFilterNone;
    // 最佳精准度 : kCLLocationAccuracyBest
    // 导航 : kCLLocationAccuracyBestForNavigation
    // 值越低精准度越高, 越耗电; 负值无效
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
    [self.locationManager requestAlwaysAuthorization];
    // 开始定位
    [self.locationManager startUpdatingLocation];
  } else {
    NSLog(@"定位服务不可用, 请设置");
  }
}

- (void)setupView {
  self.title = @"安全区域";
  
  UIView *bottomView = [[UIView alloc] init];
  [self.view addSubview:bottomView];
  bottomView.backgroundColor = [UIColor whiteColor];
  [bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.right.equalTo(self.view);
    make.bottom.equalTo(self.mas_bottomLayoutGuide);
    make.height.mas_equalTo(80);
  }];
  
  UIImageView *radiusImgV = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_radius"]];
  [bottomView addSubview:radiusImgV];
  [radiusImgV mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.equalTo(bottomView).offset(16);
    make.height.width.mas_equalTo(15);
  }];
  
  _radiusLb = [[UILabel alloc] init];
//  _radiusLb.text = @"当前安全半径:1000米";
  _radiusLb.textColor = [UIColor blackColor];
  _radiusLb.font = [UIFont fontWithName:@"PingFangSC-Regular" size:14];
  [bottomView addSubview:_radiusLb];
  [_radiusLb mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(radiusImgV.mas_right).offset(9);
    make.centerY.equalTo(radiusImgV);
    make.right.equalTo(bottomView).offset(-10);
  }];
  
  UIImageView *addressImgV = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_radius"]];
  [bottomView addSubview:addressImgV];
  [addressImgV mas_makeConstraints:^(MASConstraintMaker *make) {
    make.leading.equalTo(radiusImgV);
    make.top.equalTo(radiusImgV.mas_bottom).offset(11);
    make.height.width.mas_equalTo(15);
  }];
  
  self.addressLb = [[UILabel alloc] init];
//  self.addressLb.text = @"北京市海淀区新中关购物中心";
  self.addressLb.textColor = [UIColor blackColor];
  self.addressLb.font = [UIFont fontWithName:@"PingFangSC-Regular" size:14];
  [bottomView addSubview:self.addressLb];
  [self.addressLb mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(addressImgV.mas_right).offset(9);
    make.centerY.equalTo(addressImgV);
    make.right.equalTo(bottomView).offset(-10);
  }];
  
  self.mapView = [[MKMapView alloc] init];
  self.mapView.delegate = self;
  self.mapView.mapType = MKMapTypeStandard; //地图的类型 标准
  self.mapView.showsCompass = YES;  //显示指南针
  self.mapView.showsScale = YES;  //显示比例尺
  self.mapView.showsTraffic = YES;  //显示交通状况
  self.mapView.showsBuildings = YES;  //显示建筑物
  self.mapView.showsUserLocation = NO; //显示用户所在的位置
  self.mapView.showsPointsOfInterest = YES; //显示感兴趣的东西
  [self.view addSubview:self.mapView];
  [self.mapView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.right.equalTo(self.view);
    make.bottom.equalTo(bottomView.mas_top);
  }];
  
  UIButton *backUserLocBtn = [UIButton buttonWithType:UIButtonTypeCustom];
  [backUserLocBtn setImage:[UIImage imageNamed:@"icon_GPS"] forState:UIControlStateNormal];
  [backUserLocBtn addTarget:self action:@selector(clickBack) forControlEvents:UIControlEventTouchUpInside];
  [self.mapView addSubview:backUserLocBtn];
  [backUserLocBtn mas_makeConstraints:^(MASConstraintMaker *make) {
    make.right.bottom.equalTo(self.mapView).offset(-10);
    make.width.height.mas_equalTo(44);
  }];
  
  // 添加长按手势 切换聚焦
  UILongPressGestureRecognizer *lpress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
  lpress.minimumPressDuration = 0.5;
  [_mapView addGestureRecognizer:lpress];
  
  // 添加搜索框
  UISearchBar *searchBar = [[UISearchBar alloc] init];
  searchBar.delegate = self;
  [self.mapView addSubview:searchBar];
  [searchBar mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(self.mapView).offset(kStatusHeight);
    make.left.equalTo(self.mapView).offset(20);
    make.right.equalTo(self.mapView).offset(-20);
    make.height.mas_equalTo(44);
  }];
}

#pragma mark - 点击右下角按钮, 返回用户定位
- (void)clickBack {
  _followUserLoc = YES;
  [self focusMapTo:self.userCoordinate];
}

#pragma mark - 拖动半径大小
- (void)changedRadius:(UIButton *)button {
  CGPoint point = [button convertPoint:button.center toView:self.mapView];
  CLLocationCoordinate2D coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
  CLLocation *currentloc = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
  CLLocation *loc = [[CLLocation alloc] initWithLatitude:self.currentAnnotationView.annotation.coordinate.latitude longitude:self.currentAnnotationView.annotation.coordinate.longitude];
  CGFloat mi = [currentloc distanceFromLocation:loc];
  _radiusLb.text = [NSString stringWithFormat:@"当前安全半径:%.1f米", mi];
}

#pragma mark - 长按手势
- (void)longPress:(UIGestureRecognizer *)gestureRecognizer {
  if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
    CGPoint touchPoint = [gestureRecognizer locationInView:self.mapView];
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];
    [self focusMapTo:coordinate];
    return;
  }
}

#pragma mark - 点击搜索
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
  CLGeocoder *geocoder = [[CLGeocoder alloc]init];
  NSString *addressStr = searchBar.text;  //位置信息
  // 地理编码
  [geocoder geocodeAddressString:addressStr completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
    if (error!=nil || placemarks.count==0) {
      return ;
    }
    //创建placemark对象
    CLPlacemark *placemark = [placemarks firstObject];
    [self focusMapTo:placemark.location.coordinate];
  }];
  [searchBar endEditing:YES];
}

#pragma mark - 返回大头针
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
  if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
    WWAnnotationView *annotationView = (WWAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"WWAnnotationView"];
    if (!annotationView) {
      annotationView = [[WWAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"WWAnnotationView"];
      annotationView.delegate = self;
    }
    _currentAnnotationView = annotationView;
    return annotationView;
  }
  return nil;
}

#pragma mark - 获得用户定位
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
  CLLocation *newLocation = locations.lastObject;
  self.userCoordinate = newLocation.coordinate;
  MKCoordinateSpan span = MKCoordinateSpanMake(0.01, 0.01); //地图显示比例尺
  MKCoordinateRegion region = MKCoordinateRegionMake(newLocation.coordinate, span); //地图显示区域
  [self.mapView setRegion:region];
  [self.mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES]; // follow
  [self.locationManager stopUpdatingLocation];

  [self reverseGeocodeWith:manager.location];  // 反地理编码
}

#pragma mark - 用户定位更新了
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
  self.userCoordinate = userLocation.coordinate;
  if (_followUserLoc) {
    [self focusMapTo:userLocation.coordinate];
    _followUserLoc = NO;
  }
}

#pragma mark - 聚焦到...
- (void)focusMapTo:(CLLocationCoordinate2D)coordinate {
  MKCoordinateSpan span = MKCoordinateSpanMake(0.01, 0.01); //地图显示比例尺
  MKCoordinateRegion region = MKCoordinateRegionMake(coordinate, span); //地图显示区域
  NSLog(@"focusMapTo %f %f", coordinate.latitude, coordinate.longitude);
  NSArray *anns = self.mapView.annotations;
  [self.mapView removeAnnotations:anns];
  for (id ann in self.mapView.annotations) {
    if (![ann isKindOfClass:[MKUserLocation class]]) {
      [self.mapView removeAnnotation:ann];
    }
  }
  
  MKPointAnnotation *ann = [[MKPointAnnotation alloc] init];
  ann.coordinate = coordinate;
  [self.mapView addAnnotation:ann];
  [self.mapView setRegion:region];
  [self.mapView setUserTrackingMode:MKUserTrackingModeNone animated:YES];
  
  CLLocation *loc = [[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
  [self reverseGeocodeWith:loc];  // 反地理编码
}

#pragma mark - 反地理编码 (更新label text)
- (void)reverseGeocodeWith:(CLLocation *)location {
  CLGeocoder *gecoder = [[CLGeocoder alloc] init];
  __weak typeof(self) weakSelf = self;
  [gecoder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
    if (error != nil || placemarks.count == 0) {
      NSLog(@"%@", error);
      return;
    }
    for (CLPlacemark *placemark in placemarks) {
      NSDictionary *addressDic = placemark.addressDictionary;
      NSString *addressStr = [NSString stringWithFormat:@"%@%@%@", addressDic[@"City"], addressDic[@"SubLocality"], addressDic[@"Street"]];
      weakSelf.addressLb.text = addressStr;
    }
  }];
}

#pragma mark - 为了检测地图放大缩小, 更新半径距离
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
  if (self.currentAnnotationView && [self.currentAnnotationView isKindOfClass:[WWAnnotationView class]]) {
    [self changedRadius:self.currentAnnotationView.touchBtn];
  }
}

@end

//  WWAnnotationView.h

代码语言:javascript复制
#import <MapKit/MapKit.h>

@protocol WWWAnnotationViewDelegate <NSObject>
- (void)changedRadius:(UIButton *)button;
@end

@interface WWAnnotationView : MKAnnotationView

@property (nonatomic, weak) id<WWWAnnotationViewDelegate> delegate;
@property (nonatomic, strong) UIButton *touchBtn;

@end

//  WWAnnotationView.m

代码语言:javascript复制
#import "WWAnnotationView.h"
#import <Masonry/Masonry.h>

#define kMaxWidth ([UIScreen mainScreen].bounds.size.width-20)
static const CGFloat kDefaultWidth = 80;

@interface WWView : UIView
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, copy) void(^changedRadius)(UIButton *button);
@end

@implementation WWView {
  UIView *_view;
  CAShapeLayer *_shapeLayer;
}

- (instancetype)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    self.userInteractionEnabled = YES;
    _view = [[UIView alloc] init];
    _view.layer.borderColor =  [UIColor blueColor].CGColor;
    _view.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:.3];
    _view.layer.cornerRadius = (kDefaultWidth - 24) * 0.5;
    [self addSubview:_view];
    [_view mas_makeConstraints:^(MASConstraintMaker *make) {
      make.edges.equalTo(self).insets(UIEdgeInsetsMake(12, 12, 12, 12));
    }];
    
    _button = [UIButton buttonWithType:UIButtonTypeCustom];
    [_button setImage:[UIImage imageNamed:@"Oval 3"] forState:UIControlStateNormal];
    _button.adjustsImageWhenHighlighted = NO;
    [self addSubview:_button];
    [_button mas_makeConstraints:^(MASConstraintMaker *make) {
      make.width.height.mas_equalTo(24);
      make.right.equalTo(self);
      make.centerY.equalTo(self);
    }];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
    [_button addGestureRecognizer:pan];
    
  }
  return self;
}

- (void)layoutSubviews {
  [super layoutSubviews];
  [self drawDashLine:_view lineLength:10 lineSpacing:5 lineColor:[UIColor orangeColor]];
}

#pragma mark - 拖拽手势
- (void)panAction:(UIPanGestureRecognizer *)pan {
  CGPoint p = [pan translationInView:pan.view];
  CGFloat x = p.x;
  if (x > 5) x = 5;
  if (x < -5) x = -5;
  
  CGFloat orginWidth = self.frame.size.width;
  CGFloat resultWidth = orginWidth   x;
  
  CGFloat width;
  if (resultWidth < kDefaultWidth) {
    width = kDefaultWidth;
  } else if (resultWidth > kMaxWidth) {
    width = kMaxWidth;
  } else {
    width = resultWidth;
  }
  
  [self mas_updateConstraints:^(MASConstraintMaker *make) {
    make.width.height.mas_equalTo(width);
  }];
  _view.layer.cornerRadius = (width - 24) * 0.5;
  if (self.changedRadius) {
    self.changedRadius(_button);
  }
}

/**
 ** lineView:       需要绘制成虚线的view
 ** lineLength:     虚线的宽度
 ** lineSpacing:    虚线的间距
 ** lineColor:      虚线的颜色
 **/
- (void)drawDashLine:(UIView *)lineView lineLength:(int)lineLength lineSpacing:(int)lineSpacing lineColor:(UIColor *)lineColor {
  if (_shapeLayer) {
    [_shapeLayer removeFromSuperlayer];
  }
  CAShapeLayer *shapeLayer = [CAShapeLayer layer];
  [shapeLayer setBounds:CGRectMake(0, 0, lineView.bounds.size.width, lineView.bounds.size.height)];
  [shapeLayer setPosition:CGPointMake(CGRectGetWidth(lineView.frame) * 0.5, CGRectGetHeight(lineView.frame))];
  [shapeLayer setFillColor:[UIColor clearColor].CGColor];
  // 设置虚线颜色为blackColor
  [shapeLayer setStrokeColor:lineColor.CGColor];
  // 设置虚线宽度
  [shapeLayer setLineWidth:3];
  [shapeLayer setLineJoin:kCALineJoinRound];
  // 设置线宽,线间距
  [shapeLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:lineLength], [NSNumber numberWithInt:lineSpacing], nil]];
  // 设置路径
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathMoveToPoint(path, NULL, CGRectGetWidth(lineView.frame)*.5, 0);
  CGPathAddLineToPoint(path, NULL,CGRectGetWidth(lineView.frame), 0);
  [shapeLayer setPath:path];
  CGPathRelease(path);
  // 把绘制好的虚线添加上来
  [lineView.layer addSublayer:shapeLayer];
  _shapeLayer = shapeLayer;
}

@end


@implementation WWAnnotationView {
  WWView *_shawView;
  CGFloat _preWidth;
  CGFloat scale;
}

- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
  self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
  if (self) {
    self.image = [UIImage imageNamed:@"icon_中心定位点"];
    _shawView = [[WWView alloc] init];
    __weak typeof(self) weakSelf = self;
    _shawView.changedRadius = ^(UIButton *button) {
      if (weakSelf.delegate) {
        [weakSelf.delegate changedRadius:button];
      }
    };
    self.touchBtn = _shawView.button;
    [self addSubview:_shawView];
    [_shawView mas_makeConstraints:^(MASConstraintMaker *make) {
      make.center.equalTo(self);
      make.width.height.mas_equalTo(kDefaultWidth);
    }];
  }
  return self;
}

#pragma mark - 扩大点击范围
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
  [self layoutIfNeeded];
  if (CGRectContainsPoint(_shawView.frame, point) || CGRectContainsPoint(self.frame, point)) {
    return YES;
  }
  return NO;
}

@end

Demo下载地址

0 人点赞