详解iOS集成GoogleMap(定位、搜索)

简介:

最近花了些时间看了GoogleMap官方文件并集成到国际版app中,网上关于GoogleMap for iOS的讲解相对Android来说少一点,比较有帮助的几乎全是英文文档。下面是我开发过程中遇到的坑、以及采用的解决方法。

集成GoogleMap步骤:

  1. 1、Cocoapods导入pod 'GoogleMaps'
  2. 2、获取API密匙(前提是已经在GoogleMapSDK中创建好自己的应用)
  3. 3、配置plist文件搭建定位环境
  4. 4、调用代理方法实现需求

tips:pod 'GoogleMaps'、pod 'GooglePlaces'、pod 'GooglePlacePicker'这三个框架。(GoogleMaps:显示基本的定位功能;GooglePlaces:实现搜索功能,官方文档叫做地点自动完成;GooglePlacePicker:是实现获取某个POI的的详细信息,比如名字、详细地址、路线等)

景点(POI)包括公园、学校和政府大楼,等等。 另外,如果地图类型为 kGMSTypeNormal,商家景点默认将显示在地图上。 商家景点表示商店、餐馆和酒店之类的商家。

按照 Google Places API 中的定义,一个 POI 对应于一个地点。 例如,休闲公园为景点,但喷泉之类的地点通常不属于景点(除非它们具有国家或历史意义)。

配置plist文件:

打开plist的代码源文件,输入:

定位:

一、在AppDelegate 头文件 导入框架

#import

二、向您的 application:didFinishLaunchingWithOptions: 方法添加以下内容,使用我们刚才获取到的 API 密钥替代 YOUR_API_KEY:

[GMSServices provideAPIKey:@"YOUR_API_KEY"];

tips:这一步是在启动app的时候,GoogleMap准备代理工作。

三、在我们需要显示地图的控制器调用API方法

@property (nonatomic,strong) CLLocationManager *locationManager;//地图定位对象
@property (nonatomic,strong) GMSMapView *mapView;//地图
@property (nonatomic,strong) GMSMarker *marker;//大头针
@property (nonatomic,strong) GMSPlacesClient * placesClient;//可以获取某个地方的信息
//注册的代理
@interface TestMapViewController ()

tips:这是在控制器.h文件声明的属性。

(一)初始化一个地图对象

GMSMapView:是控制地图的外观类

GMSCameraPosition:是控制地图要显示的内容类

GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-23.12960481 longitude:113.30887721            zoom:Level];
 self.mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
 self.mapView.delegate = self; //注册代理属性
 self.mapView.settings.compassButton = YES;//显示指南针
 [self.view addSubview:self.mapView];

tips:上面的经纬度可以随便传一个,之后会获取到新的经纬度并更新位置

(二)初始化一个定位管理者对象

if (self.locationManager == nil) {
  self.locationManager = [[CLLocationManager alloc]init];
 }
 self.locationManager.delegate = self;
 [self.locationManager requestAlwaysAuthorization];//授权方式,如果在后台也需要定位,那就选择 requestAlwaysAuthorization。
 self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;//最精确的定位
 self.locationManager.distanceFilter = kCLDistanceFilterNone; // 默认是kCLDistanceFilterNone,也可以设置其他值,表示用户移动的距离小于该范围内就不会接收到通知
 [self.locationManager startUpdatingLocation];

tips:CLLocationManager 是负责获取用户行为的类,列如获取用户当前位置信息。更多详细信息请阅览CLLocationManager。里面讲解CLLocationManager的一些应用场景并有代码实例。

运行app:这时候我们会看到并没有实景地图出来,原因是:前面提到的GMSCameraPosition类,我们并没有在定位成功之后将定位内容赋它。

GMSCameraPosition类,它是负责显示定位内容的。很重要!

(三)在定位成功的API代理方法中,获取经纬度并转成影像赋值

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
 CLLocation *curLocation = [locations lastObject];
 // 通过location 或得到当前位置的经纬度
 CLLocationCoordinate2D curCoordinate2D = curLocation.coordinate;
 GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:curCoordinate2D.latitude longitude:curCoordinate2D.longitude zoom:Level];
 CLLocationCoordinate2D position2D = CLLocationCoordinate2DMake(curLocation.coordinate.latitude, curLocation.coordinate.longitude);
 self.mapView.camera = camera;//这句话很重要很重要,将我们获取到的经纬度转成影像并赋值给地图的camera属性
 [self.locationManager stopUpdatingLocation];//定位成功后停止定位
}

tips:locationManager: didUpdateLocations: 代理方法是GoogleMap 中实现定位成功后回调的代理方法,你可以在这里获取到经纬度。

运行app:这时候地图就出来了

添加大头针

GMSMarker类是负责显示大头针,默认是红色,你可以自定义大头针,用图片或者改变颜色,具体看官方文档GMSMarker。

 self.marker = [GMSMarker markerWithPosition:position2D];
 self.marker.map = self.mapView;

tips:position2D是在定位成功之后转换得到的CLLocationCoordinate2D属性经纬度值。

小坑提示:这时候有可能会出现,定位成功之后出现多个大头针。原因是:进行定位的时候,map获取多个预测位置,从而产生生成多个大头针的现象。解决办法:在每次生成大头针之前先清除之前的那个,只生成最精准的最后一个。

[self.marker.map clear];
 self.marker.map = nil;

反编码(经纬度转成具体位置):

 CLGeocoder *geocoder = [[CLGeocoder alloc]init];
 //反地理编码
 [geocoder reverseGeocodeLocation:curLocation completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {

  if (error) {
  }else{

    CLPlacemark *placemark = [placemarks objectAtIndex:0];//第一个位置是最精确的
    //赋值详细地址
    DLog(@"placemark---路号name:%@-市locality:%@-区subLocality:%@-省administrativeArea:%@-路thoroughfare:%@",placemark.name,placemark.locality,placemark.subLocality,placemark.administrativeArea,placemark.thoroughfare);

  }];

这时候就已经可以获取到具体的国家、省、市、区、街道了。

补充:反编码是获取不到POI位置的(我获取不到)。这时候可以使用

self.placesClient = [GMSPlacesClient sharedClient];//获取某个地点的具体信息
 [self.placesClient currentPlaceWithCallback:^(GMSPlaceLikelihoodList *likelihoodList, NSError *error) {
  if (error != nil) {
   DLog(@"Current Place error %@", [error localizedDescription]);
   return;
  }

//  for (GMSPlaceLikelihood *likelihood in likelihoodList.likelihoods) {
//   GMSPlace* place = likelihood.place;
//   NSLog(@"Current Place name %@ at likelihood %g", place.name, likelihood.likelihood);
//   NSLog(@"Current Place address %@", place.formattedAddress);
//   NSLog(@"Current Place attributions %@", place.attributions);
//   NSLog(@"Current PlaceID %@", place.placeID);
//  }
   //这里就可以获取到POI的名字了
   //这里做一些你想做的事

 }];

点击地图并移动大头针

这里是用到GMSMapViewDelegate的代理回调

回调1:这里是点击地图上的某个点API返回的代理方法,在这个代理方法,你可以获取经纬度去反编译地址

- (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate{
 //点击一次先清除上一次的大头针
 [self.marker.map clear];
 self.marker.map = nil;
 // 通过location 或得到当前位置的经纬度
 GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:coordinate.latitude longitude:coordinate.longitude zoom:Level];
 CLLocationCoordinate2D position2D = CLLocationCoordinate2DMake(coordinate.latitude,coordinate.longitude);
 self.mapView.camera = camera;
 //大头针
 self.marker = [GMSMarker markerWithPosition:position2D];
 self.marker.map = self.mapView;
 CLLocation *curLocation = [[CLLocation alloc]initWithLatitude:coordinate.latitude longitude:coordinate.longitude];

 CLGeocoder *geocoder = [[CLGeocoder alloc]init];
 //反地理编码
 [geocoder reverseGeocodeLocation:curLocation completionHandler:^(NSArray * _Nullable placemarks, NSError * _Nullable error) {

  if (error) {

   DLog(@"error.description:%@",error.description);

  }else{

   CLPlacemark *placemark = [placemarks objectAtIndex:0];

   //赋值详细地址
   DLog(@"placemark---路号name:%@-市locality:%@-区subLocality:%@-省administrativeArea:%@-路thoroughfare:%@",placemark.name,placemark.locality,placemark.subLocality,placemark.administrativeArea,placemark.thoroughfare);
 }];
}

回调2:这里也是点击地图上的某个点API返回的代理方法

- (void)mapView:(GMSMapView *)mapView
didTapPOIWithPlaceID:(NSString *)placeID
   name:(NSString *)name
  location:(CLLocationCoordinate2D)location{
}

tips:值得注意的,两者的区别是:第二个点击代理方法是当你点击POI的时候才会回调,会返回place的name、ID、经纬度;第一个代理方法是只要点击地图任意一个位置就会回调,只会返回经纬度。也就是:每一次的点击,只会执行其中一个代理方法。

搜索:

搜索功能在官方文档是叫做“自动完成”,即你输入一部分的文本,GoogleMap会根据你的文本预测出地点并自动填充返回,具体请看官方文档自动完成

效果如图:

这里你需要做的步骤跟做“定位”的一样:

(1)获取APIKEY

(2) 在application:didFinishLaunchingWithOptions: 注册密匙

1[GMSPlacesClient provideAPIKey:@"YOUR_API_KEY"];

(3) 创建搜索UI并调用代理方法获取API自动填充的结果数组集

小坑提示: GMSPlacesClient跟GMSServices的密匙是不一样的,密匙不对的话,会出现反复调用

viewController:didFailAutocompleteWithError:的现象。

tips:搭建搜索UI又几种方式:1)搜索框直接创建在导航栏 2)搜索栏创建在视图顶部 3)自定义。根据你的需求用代码~

(一)这里是第一种方式(搜索框直接创建在导航栏):

GMSAutocompleteViewController *acController = [[GMSAutocompleteViewController alloc] init];
 acController.delegate = self;
 [self presentViewController:acController animated:YES completion:nil];

tips:这里就可以直接往搜索框编辑文字,API会直接给你返回搜索结果集合

(二)调用API代理方法:

// Handle the user's selection. 这是用户选择搜索中的某个地址后返回的结果回调方法
- (void)viewController:(GMSAutocompleteViewController *)viewController
didAutocompleteWithPlace:(GMSPlace *)place {

 [self dismissViewControllerAnimated:YES completion:nil];
 [self.marker.map clear];
 self.marker.map = nil;
 // 通过location 或得到当前位置的经纬度
 GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:place.coordinate.latitude longitude:place.coordinate.longitude zoom:Level];
 CLLocationCoordinate2D position2D = CLLocationCoordinate2DMake(place.coordinate.latitude,place.coordinate.longitude);
 self.marker = [GMSMarker markerWithPosition:position2D];
 self.mapView.camera = camera;
 self.marker.map = self.mapView;

 self.locationLabel.text = place.name;
 self.locationDetailLabel.text = place.formattedAddress;

}

tips:这个代理方法实现的是,当用户在搜索集中选择了在某一个结果返回地图,并定位添加大头针。

自动填充失败的回调:

- (void)viewController:(GMSAutocompleteViewController *)viewController
didFailAutocompleteWithError:(NSError *)error {
 [self dismissViewControllerAnimated:YES completion:nil];
 // TODO: handle the error.
 DLog(@"Error: %@", [error description]);
}

tips:自动填充失败后你可以在这里做一些事,默认是不管的。

补充:搜索栏的外观是可以自定义的,你可以设置成跟自己的app一样的风格~具体请看设置 UI 控件样式属性

到这里,搜索功能就算完成了。

(0)

相关推荐

  • 详解iOS集成GoogleMap(定位、搜索)

    简介: 最近花了些时间看了GoogleMap官方文件并集成到国际版app中,网上关于GoogleMap for iOS的讲解相对Android来说少一点,比较有帮助的几乎全是英文文档.下面是我开发过程中遇到的坑.以及采用的解决方法. 集成GoogleMap步骤: 1.Cocoapods导入pod 'GoogleMaps' 2.获取API密匙(前提是已经在GoogleMapSDK中创建好自己的应用) 3.配置plist文件搭建定位环境 4.调用代理方法实现需求 tips:pod 'GoogleMa

  • 详解iOS集成融云SDK即时通讯整理

    最近很少写一下项目总结了,最近项目虽然做了很多,但是都是一些外包项目,做下来也没有什么值得总结的.最近一个项目用到了融云即时通讯,以前基本都是用环信,所以还遇到了一些问题,在此总结一下记录一下. 1 头像.昵称等用户信息(融云对这个问题有两种处理方式) 1.用户信息提供者 实现步骤(以下代码放在单例中,可以是AppDelegate,最好单独写一个单例) 首先遵守RCIMUserInfoDataSource这个协议 然后是要设置代理 [[RCIM sharedRCIM] setUserInfoDa

  • 详解IOS UITableViewCell 的 imageView大小更改

    详解IOS UITableViewCell 的 imageView大小更改 实例代码: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCell

  • 详解IOS 利用storyboard修改UITextField的placeholder文字颜色

    详解IOS 利用storyboard修改UITextField的placeholder文字颜色 最近有个需求需要修改UITextField的placeholder文字颜色,在网上找发现有用代码修改的,但是考虑到更加优雅的实现,所以尝试着在storyboard中直接实现,结果竟然真的成功了, 实现的位置如下: 具体步骤: 1.在User Defined Runtime Attributes中添加一个Key. 2.输入Key Path(这里我们输入_placeholderLabel.textColo

  • 详解iOS Method Swizzling使用陷阱

    在阅读团队一项目源码时,发现Method Swizzling的写法有些瑕疵.这篇文章主要就介绍iOS Method Swizzling的正确写法应该是什么样的. 下面是iOS Method Swizzling的一种实现: + (void)load { Class class = [self class]; SEL fromSelector = @selector(func); SEL toSelector = @selector(easeapi_func); Method fromMethod

  • 详解MybatisPlus集成nacos导致druid连接不上数据库

    问题 mp加密与druid和nacos结合,首次项目启动成功,后续访问无法连接数据库 导致原因 项目首次加载由于会去nacos读取一遍配置,刚好mp启动的时候也会去读取配置好key值,所以启动的时候不会报错 由于nacos有自动刷新配置功能,后面自动刷新的时候mp不会再读取命令行配置key,导致无法解密,从而连接数据库失败 解决方案 知道原因之后,我们可以修改druid连接数据库的配置,因为druid自带数据库加解密,参考ConfigFilter类就可以知道,druid会去读取外部的配置文件,可

  • 详解Springboot集成sentinel实现接口限流入门

    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面. 在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数:或者按照某种规则进行限流,如限制ip的单位时间访问次数等. 之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善.来看一下sentinel的简介: https://github.com/spring-cloud-incubator/spring-cloud-alibab

  • 详解Spring集成Redis的两种方式

    目录 一.使用Jedis方式集成 1.增加依赖 2.配置项 3.配置连接池 4.测试 使用spring-data-redis 1.引入依赖 2.配置项 3.使用 4.可能会遇到的坑 哨兵和集群 总结: 在工作中,我们用到分布式缓存的时候,第一选择就是Redis,今天介绍一下SpringBoot如何集成Redis的,分别使用Jedis和Spring-data-redis两种方式. 一.使用Jedis方式集成 1.增加依赖 <!-- spring-boot-starter-web不是必须的,这里是为

  • 详解iOS 实现一对多代理方案

    目录 实现方案一 实现方案二 实现方案一 利用可变数组. 签协议方需要add到代理的数组中, 然后协议遍历数组中的对象,进行分发.缺点是需要数组对其内部元素是强引用, 需要在合适的地方对其进行释放,否则会有内存泄漏 代理协议的对象.h写法 #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @protocol TestSubViewDelegate <NSObject> - (void)testSendSomeMessageToOther

  • 详解SpringBoot集成消息队列的案例应用

    目录 背景 方案规划 统一设计 集成Redis消息队列 集成ActiveMQ消息队列 使用示例 背景 最近在对公司开发框架进行优化,框架内涉及到多处入库的日志记录,例如登录日志/操作日志/访问日志/业务执行日志,集成在业务代码中耦合度较高且占用业务操作执行时间,所以准备集成相关消息队列进行代码解耦 方案规划 现有的成熟消息队列组件非常多,例如RabbitMQ,ActiveMQ,Kafka等,考虑到业务并发量不高且框架已经应用于多个项目平稳运行,准备提供基于Redis的消息队列和集成ActiveM

随机推荐