MKMapView遇到的问题

  1. setCenterCoordinate和setRegion方法不能同时或短时间内同时执行
  2. 需求:大头针根据CLLocationManager代理的方位指示,并且会随着CLLocationManager的位置变化为移动,方位指示代码如下
    self.userAnnotationView.transform = CGAffineTransformMakeRotation(self.rotateDegree);

异常现象:用户位置指针方向偶尔回到正北方向的闪烁问题
跳点原因:每次定位回调时,更改用户位置,系统会将大头针的状态回到最初的指向正北方,导致指针指向出现闪烁变化(概率事件)
解决方法:将当前旋转角度作为全局变量保存,每次定位回调的时候再次赋值

  1. 判断用户是否拖拽过地图。
    地图因为自身手势屏蔽了系统的Touch事件,导致Touch事件无法完整监听。而手动添加手势也不会生效(通过设置代码方法也可以使其生效,但是麻烦),有个取巧,经过测试,发现每次的拖拽地图事件,都会触发touchesCancelled方法,而地图点击事件不会触发,这里判断最简单

  2. 内存问题。
    地图销毁时通常执行如下代码回收内存

- (void)clearMapView {
    self.showsUserLocation = NO;
    self.delegate = nil;
    [self removeAnnotations:self.annotations];
    [self removeOverlays:self.overlays];
    [self removeFromSuperview];
}

但是地图在缩放,或者移动过程中内存还是会持续增大,无法有效控制。根据文档,发现切换地图的类型会删除地图缓存,牺牲GPU重绘换取内存优化。

   self.mapType = MKMapTypeHybrid;
   self.mapType = MKMapTypeStandard;

这段代码也有弊端,会使地图出现短暂的空白再绘制现象,影响体验。手机越好,影响越小,在iphonex上面影响已经非常小了。考虑到地图内存在缩放时增加最快,可以在代理方法regionDidChangeAnimated中判断是否是缩放事件,且地图缩放达到一定比例才执行上面的释放内存方法。
通过该方法,地图页面内存始终保持在相对低位

  1. 地图轨迹线出现的残影问题
    地图上的轨迹线宽度随着地图的放大而缩小,有可能会在屏幕上留下尾部伪像,地图从很小变到很大时出现概率较大,手动缩放地图时变化比例不大,出现概率很低
    当刚进入地图,而地图上绘制了轨迹线,如果使用setVisibleMapRect,setRegion等带有动画效果,则地图放大过程中轨迹线两端宽度可能来不及随地图放大而缩小,导致屏幕上留下轨迹线尾部伪像。
    解决方案:
    5.1. 取消缩放动画,这个对手动缩放地图导致的尾部伪像无能为力,但是因为手动出现概率很低,勉强使用
    5.2. 子类MKPolylineRenderer,重写applyStrokePropertiesToContext方法让轨迹线宽度恒定,但这样会导致地图缩放时线的宽度太小而看不见了
    5.3. 子类MKPolyline,重写boundingMapRect方法返回MKMapRectWorld,让轨迹线全量绘制,配合上面的效果可以

  2. 地图上大头针的显示次序问题
    地图上存在多个大头针,当地图缩放时希望某一个大头针始终在最前面

- (void)updateAnnotationzIndex{
    for (PAXMapAnnotation *anno in self.annotations) {
        MKAnnotationView *annotationView = [self viewForAnnotation:anno];
        if (annotationView.top) {
            [annotationView.superview bringSubviewToFront:annotationView];
        }else{
            [annotationView.superview sendSubviewToBack:annotationView];
        }
    }
}

在代理方法didAddAnnotationViews,和regionDidChangeAnimated(判断是缩放地图时),这两种情况下调用上面方法,效果算凑合,大多能实现所需效果,但是不完美,有时候还是会出现不希望的效果,有知道完美解决的请留言

  1. 实现用户位置在地图正中心,且能显示下地图上的所有大头针
   CLLocationDegrees maxLatitude = -MAXFLOAT;
        CLLocationDegrees maxLongitude = -MAXFLOAT;
        CLLocationDegrees minLatitude = MAXFLOAT;
        CLLocationDegrees minLongitude = MAXFLOAT;
        BOOL hasValidData = NO;
        for (PAXMapAnnotation *anno in self.annotations) {
            if (anno.annotationType > PAXMapAnnotationTypeMyLocation && anno.annotationType != PAXMapAnnotationTypeDriveCurrentPoint) continue;
            if (isSearch && anno.annotationType >= PAXMapAnnotationTypeMyLocation) continue;
            hasValidData = YES;
            CLLocationCoordinate2D coordinate = anno.coordinate;
            maxLatitude = MAX(maxLatitude, coordinate.latitude);
            maxLongitude = MAX(maxLongitude, coordinate.longitude);
            minLatitude = MIN(minLatitude, coordinate.latitude);
            minLongitude = MIN(minLongitude, coordinate.longitude);
        }
        if (!hasValidData) { // 当没有任何大头针时,防止计算出的圈没有边界
            [self setMapLevel:coordinate level:PAXMapViewZoomLevelDefault];
            return;
        }
        CLLocationDegrees offsetMaxLatitude = fabs(maxLatitude - coordinate.latitude);
        CLLocationDegrees offsetMaxLongitude = fabs(maxLongitude - coordinate.longitude);
        CLLocationDegrees offsetMinLatitude = fabs(coordinate.latitude - minLatitude);
        CLLocationDegrees offsetMinLongitude = fabs(coordinate.longitude - minLongitude);
        CLLocationDegrees deltaLatitude = 2 * MAX(offsetMaxLatitude, offsetMinLatitude) * 1.1;
        CLLocationDegrees deltaLongitude = 2 * MAX(offsetMaxLongitude, offsetMinLongitude) * 1.2;
        MKCoordinateRegion region = MKCoordinateRegionMake(coordinate, MKCoordinateSpanMake(deltaLatitude, deltaLongitude));
        [UIView animateWithDuration:0.5 animations:^{
            // 有发现听云bug,deltaLatitude是197,deltaLongitude是200从而导致MKCoordinateRegion无效闪退
            // 检查上面算法没找到逻辑问题,采用下面的过滤避免闪退
            if (CLLocationCoordinate2DIsValid(region.center) && deltaLatitude < 1 && deltaLongitude < 1){
                [self setRegion:region animated:YES];
            }else{
                [self showAnnotations:self.annotations animated:YES];
            }
        }];

原理:求出最大最小经纬度所围成的一个矩形圈,然后计算这个圈四周距离用户位置的最大的两边,将这两边和用户位置的间距*2即得跨度。(上面代码忽略了边界处理,如没有大头针时,或者大头针经纬度异常时)

  1. 动画
    地图的很多方法系统自带动画,例如setRegion,setCenterCoordinate,setVisibleMapRect,动画的时间没法控制,可以采用如下控制动画时间
  [UIView animateWithDuration:0.5 animations:^{
            [self setRegion:region animated:YES];
   }];
  1. 定位点漂移现象
    地图一般配合CLLocationManager定位,定位出现的漂移采用如下过滤
- (BOOL)filterLocation:(CLLocation *)location{
    if (location.horizontalAccuracy >= 200) return YES;
    if (location.horizontalAccuracy < 50) return NO;
    NSTimeInterval offsetNow = [location.timestamp timeIntervalSinceNow];
    if (offsetNow >= 20) return YES;
    CLLocationDistance meter = [location distanceFromLocation:self.currentLocation];
    NSTimeInterval offsetTime = [location.timestamp timeIntervalSinceDate:self.currentLocation.timestamp];
    CGFloat speed = meter / offsetTime;
    BOOL isAutoDriveing = [Globals sharedInstance].driverSegment == segment_drivering;
    BOOL isManualDriveing = [Globals sharedInstance].manualSwitchOn;
    BOOL isDriveing = isAutoDriveing || isManualDriveing;
    if (isDriveing && speed > 41.6) return YES;
    if (!isDriveing && (speed > 17 / 3.6 * 1.3)) return YES;
    return NO;
}

采用定位精度,定位缓存时间,判断非开车状态速度上限等过滤。我这里能知道是否开车,所以能设置速度上限,过滤漂移严重的点。

  1. 大头针重复删除添加优化
    我这里需求是,移动地图不断加载附近一公里的停车位在地图上显示出来。为了减少请求数量,判断地图移动达到一个阀值(如300m)才发起新的请求,两次请求会有很多重复的数据,简单的方法是,请求回来删除地图上的所有大头针,再添加新的。因为删除添加大头针是UI操作,在滑动地图时会有卡顿现象。
    优化:判断两次请求数据差异,删除新的请求没有了的数据对应的大头针,同时添加新的请求增加了的数据对应的大头针,对两次请求共有的大头针不做处理。
datas = [datas sortedArrayUsingComparator:^NSComparisonResult(id<PAXBinarySearchProtocol> o1, id<PAXBinarySearchProtocol> o2) {
        return o1.ID > o2.ID;
    }];
    NSMutableSet *containModels = [NSMutableSet set];
    // 移除旧的大头针
    // 只需要移除消失了的数据对应的大头针,同时添加未有得数据
    // 例:原有大头针对应的模型为1,3,5,现接受到的数据为1,4,5,则移除3大头针,同时添加4大头针即可
    for (PAXMapAnnotation *annotation in self.mapView.annotations) {
        NSInteger findIndex = [self binarySearch:datas target:annotation.model];
        if (findIndex == kBinarySearchNotFind) {
            [self.mapView removeAnnotation:annotation];// 例: 移除3
        }else{
            [containModels addObject:datas[findIndex]];// 例:[1,5]
        }
    }
    PAXMapAnnotation *annotation;
    for (id model in datas) {// 添加新的大头针
        if ([containModels containsObject:model]) continue;  // 例:添加4
        annotation = [PAXMapAnnotation annotationWithType:type coordinate:[model coordinate] model:model];
        [self.mapView addAnnotation:annotation];
    }

binarySearch就是一个简单的二分搜索方法

- (NSInteger)binarySearch:(NSArray<id<PAXBinarySearchProtocol>> *)array target:(id<PAXBinarySearchProtocol>)target{
    if (!array.count) return kBinarySearchNotFind;
    if (![[array lastObject] isKindOfClass:[target class]]) return kBinarySearchNotFind;
    NSInteger low = 0;
    NSInteger high = array.count - 1;
    while (low <= high) {
        NSInteger mid = low + ((high - low)/2);
        id<PAXBinarySearchProtocol> num = array[mid];
        if (num.ID == target.ID) {
            return mid;
        }else if (num.ID > target.ID){
            high = mid - 1;
        }else{
            low = mid +1;
        }
    }
    return kBinarySearchNotFind;
}

二分搜索的数组需要排序,因为我这里有两种不同的模型需要共用,定义了一个二分搜索的专门协议

@protocol PAXBinarySearchProtocol <NSObject>
@property (nonatomic, assign, readonly) NSInteger ID; // 用来排序,二分搜索用
@end
  1. 同一个请求依次多次请求,返回顺序问题
    地图移动时会不断发送请求,有时候会出现后面发出的请求先回来,前面发出的请求后回来现象,导致页面显示异常。
    举个栗子:顺序发出1,2,3请求,若响应的顺序是1,3,2,希望达到处理1,3响应,丢弃2响应的目的
    static NSInteger requestIndex = 0;
    static NSInteger responseIndex = 0;
    NSInteger currentRequestIndex = ++requestIndex;
    [HTTP POST:params result:^(NSError *error, id responseData) {   
        if (responseIndex > currentRequestIndex) return;
        responseIndex = currentRequestIndex;
    }

原理:block捕获局部变量为值捕获

若不是block而是代理,如采用高德搜索,当连续输入搜索, 不断发起搜索时

static NSString *k_pax_searchRequestIndex = @"k_pax_searchRequestIndex";
static NSInteger _requestIndex = 0;
static NSInteger _responseIndex = 0;
- (void)searchHeaderViewTextDidChangeAction:(NSString *)text{
    AMapInputTipsSearchRequest *tipReq = [[AMapInputTipsSearchRequest alloc] init];
    tipReq.keywords = text;
    _requestIndex++;
    objc_setAssociatedObject(tipReq, &k_pax_searchRequestIndex, @(_requestIndex), OBJC_ASSOCIATION_COPY);
    [self.searchAPI AMapInputTipsSearch:tipReq];
}
- (void)onInputTipsSearchDone:(AMapInputTipsSearchRequest *)request response:(AMapInputTipsSearchResponse *)response{
    NSInteger requestIndex = [objc_getAssociatedObject(request, &k_pax_searchRequestIndex) integerValue];
    if (_responseIndex >= requestIndex) return;
    _responseIndex = requestIndex;
}
  1. 地图轨迹线毛刺现象优化
    采用道格拉斯-普克-抽稀算法,实现如下
+ (CLLocationCoordinate2D *)douglasAlgorithm:(CLLocationCoordinate2D *)coordinates count:(NSInteger *)count threshold:(CGFloat)threshold{
    if (!coordinates || *count <= 2) return coordinates;
    if (*count > 10000 && threshold < 10) return coordinates; // 避免耗时太久的优化
    
    NSMutableArray *list = [self convertCoordinatesToLatLngModels:coordinates count:*count].mutableCopy;
    NSMutableArray *points = @[list.firstObject,list.lastObject].mutableCopy;
    free(coordinates);
    
    for (NSInteger i = 0; i < points.count - 1; i++) {
        NSInteger startIndex = [list indexOfObject:points[i]];
        NSInteger endIndex = [list indexOfObject:points[i+1]];
        if ((endIndex - startIndex) == 1) continue;
        NSString *value = [self getMaxDistance:list startIndex:startIndex endIndex:endIndex threshold:threshold];
        
        if ([value componentsSeparatedByString:@","][0].doubleValue >= threshold) {
            NSInteger position = [[value componentsSeparatedByString:@","][1] integerValue];
            [points insertObject:list[position] atIndex:i + 1];
            i--; // 重新遍历当前节点
        }else {
            [list removeObjectsInRange:NSMakeRange(startIndex + 1, endIndex - startIndex - 1)];
        }
    }
    // 必须改变C数组对应的长度
    *count = points.count;
    return [self convertLatLngModelsToCoordinates:points];
}

// 由startIndex和endIndex组成的线段,计算这条线段上面的所有点距离该线段的最大距离
// 返回:“最大距离,索引”,如 @"20,5",表明最大点距离直线为20,索引为5
+ (NSString *)getMaxDistance:(NSArray <PAXLatLngModel *>*)coordinateList startIndex:(NSInteger)startIndex endIndex:(NSInteger)endIndex threshold:(CGFloat)threshold{
    CGFloat maxDistance = -1;
    NSInteger position = -1;
    CGFloat distance = [self getDistance:coordinateList[startIndex] lastEntity:coordinateList[endIndex]];
    
    for(NSInteger i = startIndex; i < endIndex; i++){
        CGFloat firstSide = [self getDistance:coordinateList[startIndex] lastEntity:coordinateList[i]];
        if(firstSide < threshold) continue;
        CGFloat lastSide = [self getDistance:coordinateList[endIndex] lastEntity:coordinateList[i]];
        if(lastSide < threshold) continue;
        // 海伦公式求距离
        CGFloat p = (distance + firstSide + lastSide) / 2.0;
        CGFloat dis = sqrt(p * (p - distance) * (p - firstSide) * (p - lastSide)) / distance * 2;
        if(dis > maxDistance){
            maxDistance = dis;
            position = i;
        }
    }
    return [NSString stringWithFormat:@"%f,%ld", maxDistance,(long)position];
}

// 两点间距离公式
+ (CGFloat)getDistance:(PAXLatLngModel*)firstEntity lastEntity:(PAXLatLngModel*)lastEntity{
    CLLocation *firstLocation = [[CLLocation alloc] initWithLatitude:firstEntity.latitude longitude:firstEntity.longitude];
    CLLocation *lastLocation = [[CLLocation alloc] initWithLatitude:lastEntity.latitude longitude:lastEntity.longitude];
    return [firstLocation distanceFromLocation:lastLocation];
}

+ (NSArray *)convertCoordinatesToLatLngModels:(CLLocationCoordinate2D *)coordinates count:(NSInteger )count{
    NSMutableArray *LatLngs = @[].mutableCopy;
    for (int i = 0; i < count; i++) {
        PAXLatLngModel *model = [PAXLatLngModel new];
        model.latitude = coordinates[i].latitude;
        model.longitude = coordinates[i].longitude;
        [LatLngs addObject:model];
    }
    return LatLngs;
}

+ (CLLocationCoordinate2D *)convertLatLngModelsToCoordinates:(NSArray<PAXLatLngModel *>*)latLngModels{
    NSInteger size = sizeof(CLLocationCoordinate2D);
    CLLocationCoordinate2D *coordinates = malloc(latLngModels.count * size);
    for (int i = 0; i < latLngModels.count; i++) {
        PAXLatLngModel *model = latLngModels[i];
        coordinates[i] = CLLocationCoordinate2DMake(model.latitude, model.longitude);
    }
    return coordinates;
}

本质为过滤点,因为改变了C数组的长度,所以相对应的改变C数组的长度count值。threshold为算法阀值,太大则会将细微转弯过滤,不行。太小则无法起到作用,需要测试调节注意,该算法为耗时操作,尤其是当count很大,而threshold很小时。测试,iphone6,深圳到北京的路途点,共17000多个点,threshold为2.0时,耗时约3.8s

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容