iOS 仿写微信发送地址模块

效果图

最近项目要用到微信发送地址查看地址的功能,完成的也差不多了,基本上大部分功能都实现了,现在整理出来,也有一些想和大家一起交流的地方,希望彼此都有所收获。

github地址

先讲讲UISearchController

说实话我之前很少有用到它,基本上都是自定义的,但是我看微信的效果虽然不肯定就是UISeachController但是基本上是差不多的,所以我也就踩踩这个坑,其实了解了以后使用还是相当方便的。

问题1:点击searchbar的时候,searchbar飞到视图外面
现象

解决办法
self.definesPresentationContext = YES;

问题2:点击searchbar的时候,searchbar上方也就是状态栏的部分是透明的,和UISearchController的颜色是一样的。
现象

这个情况在默认情况下是不会出现的,但是我们开发的使用经常使用这样一句代码self.edgesForExtendedLayout = UIRectEdgeNone;来使tableView顶到navigationbar而不是延展到navigationbar里面,并且往往在基类中做这一设置。

解决方法
self.edgesForExtendedLayout = UIRectEdgeAll;

再就是去掉searchBar底部有一条黑线

_searchController.searchBar.subviews.firstObject.subviews.firstObject.layer.borderColor = [UIColor colorWithHexString:@"c6c6c6"].CGColor;
        _searchController.searchBar.subviews.firstObject.subviews.firstObject.layer.borderWidth = .5;

地图功能

进入界面默认显示定位地址,下方的POI列表也是默认显示定位附近的,大头针是一直都指向地图的中心点,之后定位的变化不会再引起POI列表的变化,只有拖动地图,改变大头针指向的位置才会更新POI列表。

poi列表 也就是地图下方的列表

这里的POI列表,我是采用的反地理编码的方式,但是有局限性,也就是百度地图只返回最多十条记录,这就有点尴尬了,百度地图有提供其他查询附近POI的方式(像以坐标为原点xxx米为半径等),但是这些方式都有一个共同点 ,需要加入一个关键字,也就是说,我不能把附近所有的POI都列出来,只能根据类别搜索,像搜美食酒店这个意思。

暂时没找到好办法,没法像微信那样获取很多记录,有知道的望能告知。

定位

当点击右下角的定位按钮会将用户的位置显示在中心点处,百度地图有四种地位模式

1) BMKUserTrackingModeNone :普通定位模式,显示我的位置,我的位置图标和地图都不会旋转
2) BMKUserTrackingModeFollow : 定位跟随模式,我的位置始终在地图中心,我的位置图标会旋转,地图不会旋转
3) BMKUserTrackingModeFollowWithHeading : 定位罗盘模式,我的位置始终在地图中心,我的位置图标和地图都会跟着旋转
4) BMKUserTrackingModeHeading:普通定位+定位罗盘模式,显示我的位置,我的位置始终在地图中心,我的位置图标会旋转,地图不会旋转。即在普通定位模式的基础上显示方向。

我这里用的是普通定位模式,我认为发送地址重要的是找地址,并不需要了解我面朝南还是朝北。。但是在查看地址的时候,是十分有必要的。

定位这里有个小细节,就是微信在移动地图的时候,他会用当前地图的中心点用户定位的地点做比较,如果误差在一定范围,那么地图右下角表示已经在当前位置的按钮就会成选中状态。这里我直接用的百度地图的计算API,这里我设定的是100m。

    BMKMapPoint centerPoint = BMKMapPointForCoordinate(centerlocation);
    BMKMapPoint userPoint   = BMKMapPointForCoordinate(self.locService.userLocation.location.coordinate);
    CLLocationDistance distance = BMKMetersBetweenMapPoints(centerPoint,userPoint);
    if (distance <=100) {
        self.locationBtn.selected = YES;
    }else{
        self.locationBtn.selected = NO;
    }

记得将.m文件修改为.mm

搜索地址

这个地方我一开始想参考微信,输入地址,在全国检索,基于这个需求我在百度地图开发文档中找相关接口,结果发现并没有全国搜索这一个接口,只有按城市搜索,结果使用后我认为这就是我要找的。它会默认在你设置的城市中搜索,当前城市没有再在全国范围内搜索,我认为这是比较合理的,否则,单纯的全国搜索还需要考虑权重的问题,如果我想搜索 “体育馆”理所应当我想的是在当前城市搜索体育馆,如果我真想搜其他地方的体育馆,可以加上城市名搜索。

城市POI搜索代码

  BMKCitySearchOption *option = [[BMKCitySearchOption alloc]init];
    option.pageIndex = 0;
    option.pageCapacity = 30;
    option.keyword = self.keyword;//搜索关键字
    option.city = self.userCity.length == 0?@"青岛市":self.userCity; //userCity是用户定位地址
    BOOL flag = [self.poiSearch poiSearchInCity:option];
    if(flag)
    {
        NSLog(@"周边检索发送成功");
    }
    else
    {
        NSLog(@"周边检索发送失败");
    }
关键字高亮

这里我是将关键字 拆分成每一个单独的字符进行匹配的,效果如图

关键字高亮

这里有个坑我有必要提一下,一开始做这个功能的时候,我第一时间想到的是- (NSRange)rangeOfString:(NSString *)searchString; 这个方法,在字符串中查找某个字串的NSRange,但是这有个问题就是这个方法只返回第一个的位置,也就是如果这一行中有两个相同子符,他也永远只返回第一个的,那好吧,看看兄妹方法有没有能适用的,结果找到了这个方法- (NSRange)rangeOfString:(NSString *)searchString options:(NSStringCompareOptions)mask range:(NSRange)rangeOfReceiverToSearch; 这个是可以指定范围查找,看起来很完美。但是。。。。我在实际使用过程中,出现了随机的NSNotFound,但是我用containsString做了判断了,并且也是确确实实存在的。

图中可见,上下两个结构基本一样,但是第二个就出现了NSNotFound,最后的解决方案是遍历字符串自己构建NSRange,并存放到数组里,再遍历NSRange数组设置高亮

   NSMutableAttributedString *attributeStr = [[NSMutableAttributedString alloc] initWithString:origintext];
    NSMutableArray *indexArray = @[].mutableCopy;
    for(int i =0; i < [origintext length]; I++)
    {
        NSString *temp = [origintext substringWithRange:NSMakeRange(i,1)];
        if ([keyword containsString:temp]) {
            [indexArray addObject:[NSValue valueWithRange:NSMakeRange(i, 1)]];
        }
    }
    
    [indexArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSRange range = [obj rangeValue];
         [attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:23/255.0f green:182/255.0f blue:0 alpha:1] range:range];
    }];

查看地址模块


这个就比较简单了,传入需要显示的坐标,反地理编码获取POI的name,address显示出来就可以了

这里的定位模式是BMKUserTrackingModeHeading:普通定位+定位罗盘模式 主要方便导航到目的地。

这里有几个功能

  • 显示路线
  • 街景
  • 外部地图导航


显示路线

这里显示的是驾车路线,虽然不一定是最优路线,但是起码无论你步行还是骑行都是能走通的,这个在这里也只是显示个大概,真正的导航还是跳转到各类地图应用吧。

发起请求

   BMKPlanNode* start = [[BMKPlanNode alloc]init];
    start.pt = self.coordinate;
    BMKPlanNode* end = [[BMKPlanNode alloc]init];
    end.pt   = self.locationService.userLocation.location.coordinate;
    BMKDrivingRoutePlanOption *option = [[BMKDrivingRoutePlanOption alloc]init];
    option.from = start;
    option.to = end;
    
    //发起检索
    BOOL flag = [self.routeSearcher drivingSearch:option];
    if(flag) {
        NSLog(@"驾车检索发送成功");
    } else {
        NSLog(@"驾车检索发送失败");
    }
    

绘制路线

     [self.mapView removeOverlays:self.mapView.overlays];
        BMKDrivingRouteLine *plan = (BMKDrivingRouteLine *)[result.routes objectAtIndex:0];
        int size = (int)[plan.steps count];
        int pointCount = 0;
        for (int i = 0; i< size; i++) {
            BMKDrivingStep *step = [plan.steps objectAtIndex:i];
            pointCount += step.pointsCount;
        }
        BMKMapPoint *points = new BMKMapPoint[pointCount];
        int k = 0;
        for (int i = 0; i< size; i++) {
            BMKDrivingStep *step = [plan.steps objectAtIndex:i];
            for (int j= 0; j<step.pointsCount; j++) {
                points[k].x = step.points[j].x;
                points[k].y = step.points[j].y;
                k++;
            }
        }
        BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:points count:pointCount];
        [self.mapView addOverlay:polyLine];

当然你需要先在mapVIew的代理方法``(BMKOverlayView *)mapView:(BMKMapView *)mapView viewForOverlay:(id )overlay` 自定义样式

街景

这个没啥要说的,很简单,需要单独下载SDK

调用地图应用导航

这里我用的是 百度地图 ,高的地图 ,苹果地图, 默认调用的都是驾车路线,当然在地图里面可以更改,使用url的方法调用,[[UIApplication sharedApplication] openURL:url],调用之前最好用[[UIApplication sharedApplication] canOpenURL:url]判断一下,别忘了加Schemes

百度地图:baidumap
高德地图:iosamap

推荐阅读更多精彩内容