Google Maps iOS SDK 教程:入门

1,146 阅读16分钟

入门

注意:

  • MapViewController.swift:这是这个项目的主视图控制器,在本教程中你只会使用这个控制器。
  • GoogleDataProvider.swift:这是一个用于进行 Google API 调用的包装类。您将在本教程的后面部分查看它包含的方法。
  • GooglePlace.swift:这是从 Google 返回的地点结果的模型。
  • MarkerInfoView.swift:这是UIView显示地点详细信息的子类。它带有一个匹配的 xib 文件。

在开始编码之前,最好先了解一下应用程序是如何工作的。构建并运行您的应用程序;您将看到以下屏幕出现:

01.png

现在你会看到一个空白屏幕,中间有一个大头针。按导航栏右侧的操作按钮可以看到如下TypesTableViewController屏幕:

02.png

这就是目前在应用程序中看到的全部内容 - 由您来添加一些魔法!

创建 API 密钥

您首先需要的是一些用于 Google Maps SDK 的 API 密钥以及您将使用的 Google API。如果您还没有 Google 帐户,请创建一个(它们是免费的!)并登录到Google Developers Console

单击Create Project,将您的项目命名为Feed Me,然后单击Create

03.png

如果您的项目没有立即出现,请刷新页面直到出现。选择新创建的项目以访问其设置。从左侧窗格菜单中选择APIs & SERVICES,然后选择Library :

04.png

搜索并启用这些 API:

  • 适用于 iOS 的 Google 地图 SDK
  • Google Places API 网络服务

在左侧窗格菜单中的API 和服务下选择凭据。单击Create credentials,然后单击API key创建密钥:

05.png

复制密钥并单击关闭

06.png

稍后您将使用该密钥,但首先您将添加实际的 Google Maps iOS SDK。

添加 SDK

打开Podfile(在Pods项目中)并在上面添加以下内容end

吊舱“谷歌地图”

接下来,打开终端并使用以下命令导航到包含Feed Me项目的目录:cd

cd ~/Path/To/Folder/Containing/Feed Me

输入以下命令以安装 Google Maps iOS SDK:

卸载重装

您应该会看到类似于以下内容的输出:

下载依赖项
安装谷歌地图 (2.5.0)
使用 SwiftyJSON (4.0.0)
生成 Pods 项目
整合客户项目

现在,您的项目中有GoogleMaps。CocoaPods 不是让生活变得更轻松吗?:]

打开AppDelegate.swift并将其内容替换为以下内容:

导入UIKit
导入谷歌地图

// 1
让 googleApiKey =  "ENTER_KEY_HERE"

@UIApplicationMainAppDelegate : UIResponder , UIApplicationDelegate {
  
  变量窗口:UIWindow?
  
  func 应用程序(_ 应用程序:UIApplication,didFinishLaunchingWithOptions 启动选项:[ UIApplicationLaunchOptionsKey:任何] ?)->布尔{

    //2 
    GMSServices .provideAPIKey (googleApiKey)
    返回真
  }
}

这里有两个新元素:

  1. 用于保存您的 Google API 密钥的常量。替换ENTER_KEY_HERE为您之前创建的 Google API 密钥。
  2. GMSServices您的应用程序将使用类方法使用 API 密钥实例化 Google 地图服务provideAPIKey()

接下来,打开Main.storyboard以调出 Interface Builder。通过选择视图工具栏中的第三个选项卡 - 实用程序 - 来调出对象库,然后选择库工具栏中的第三个选项卡 - 对象库 - 如下面的屏幕截图所示:

07.png

找到MapViewController场景并将一个简单UIView的从对象库拖到MapViewController视图的大致中心。将视图的背景颜色更改为浅灰色。接下来,使用Editor\Show Document Outline 打开 Document Outline**并重新排列视图层次结构,使对象树如下所示:

08.png

要将这个简单UIView转换为GMSMapView,请选择您刚刚添加的视图,然后通过选择实用工具工具栏左侧的第三个选项卡来打开身份检查器。将视图的Class更改为GMSMapView,如下面的屏幕截图所示:

09.png

您的MapViewController场景现在应该如下所示:

10.png

接下来,您需要添加一些约束以使地图填满整个屏幕。在 Document Outline 中选择Map View,然后选择 Interface Builder 窗口右下角的第二个按钮 — Pin按钮。确保未选中Constrain to margins - 这确保地图将填满屏幕上的所有可用空间 - 并从 superview 的顶部、左侧、底部和右侧添加0 (零)空间约束。

您的 Pin 编辑器应如下所示:

11.png

单击Add 4 Constraints将约束添加到地图视图。 您的MapViewController场景应如下所示,其中灰色区域代表GMSMapView

12.png

在构建和运行项目之前,IBOutlet为地图视图添加一个。为此,通过选择编辑器工具栏中的第二个选项卡来调出助手编辑器:

13.png

在 Interface Builder 中选择地图视图,按住Ctrl键并从地图视图中拖出一条线到MapViewController.swift。将出现一个弹出窗口;将连接类型设置为Outlet,将名称设置为mapView. 保持 Type 为GMSMapView,然后单击Connect

14.png

这将在MapViewController.swift中创建一个GMSMapView属性,并在 Interface Builder 中自动连接它。在构建和运行之前,将以下内容添加到文件顶部,之后:import UIKit

导入谷歌地图

构建并运行您的项目;您现在应该看到一张地图,如下所示:

15.png

您现在在您的应用程序中使用 Google Maps iOS SDK — 但您可以做的不仅仅是显示基本地图,对吗?:]

获取位置

Feed Me就是寻找用户附近的地方,如果不获取用户的位置,您就无法做到这一点。

首先,打开MapViewController.swift并添加以下属性:

私有 让locationManager =  CLLocationManager ()

这将添加并实例化一个CLLocationManager名为locationManager.

接下来,找到viewDidLoad()这两行并将其添加到底部:

locationManager.delegate =  self
locationManager.requestWhenInUseAuthorization()

这将使MapViewController委托locationManager和请求访问用户的位置。

接下来,打开Project navigator 顶部的Feed Me项目,选择**Feed Me目标并转到Info选项卡,选择**Custom iOS Target Properties部分中的第一行,然后单击*+*图标以添加新行。

选择Privacy – Location When In Use Usage Description作为键,选择String作为类型,然后输入以下文本作为值:

通过访问您的位置,这个应用程序可以为您找到一个吃饭的好地方。

完成后,它应该如下所示:

16.png

在运行应用程序之前,您需要MapViewController符合CLLocationManagerDelegate协议。

将以下扩展添加到MapViewController.Swift的底部:

// MARK: - CLLocationManagerDelegate 
//1
扩展 MapViewController : CLLocationManagerDelegate {
   // 2 
  func  locationManager ( _  manager : CLLocationManager , didChangeAuthorization  status : CLAuthorizationStatus ) {
     // 3 
    guard status == .authorizedWhenInUse else {
       return
    }
    // 4
    locationManager.startUpdatingLocation()
      
    //5 
    mapView.isMyLocationEnabled =  true 
    mapView.settings.myLocationButton =  true
  }
  
  // 6 
  func  locationManager ( _  manager : CLLocationManager , didUpdateLocations 位置: [ CLLocation ]) {
     guard  let location = locations.first else {
       return
    }
      
    // 7 
    mapView.camera =  GMSCameraPosition(目标:location.coordinate,缩放:15,方位:0,viewingAngle:0// 8
    locationManager.stopUpdatingLocation()
  }
}

依次获取每个编号的评论:

  1. 您创建一个MapViewController符合CLLocationManagerDelegate.
  2. locationManager(_:didChangeAuthorization:)当用户授予或撤销位置权限时调用。
  3. 在这里,您验证用户在使用应用程序时已授予您权限。
  4. 建立权限后,请向位置管理员询问有关用户位置的更新。
  5. GMSMapView有两个关于用户位置的功能:在用户所在的位置myLocationEnabled绘制一个浅蓝色点,而myLocationButton设置为 时true,向地图添加一个按钮,点击该按钮时,地图以用户位置为中心。
  6. locationManager(_:didUpdateLocations:)当位置管理器接收到新的位置数据时执行。
  7. 这会将地图的摄像头更新为以用户当前位置为中心。该类GMSCameraPosition聚合所有相机位置参数并将它们传递给地图以进行显示。
  8. 告诉locationManager您不再对更新感兴趣;您不想跟随用户,因为他们的初始位置足以让您使用。

构建并运行您的应用程序;加载后,系统会提示您发出警报,询问位置权限。点击允许

17.png

您现在应该会看到以您所在位置为中心的地图。滚动地图并点击定位按钮,地图将回到您的位置,如下所示:

18.png

实施地理编码

现在您有了用户的位置,如果您可以显示该位置的街道地址,那就太好了。谷歌地图有一个对象可以做到这一点:GMSGeocoder. 这需要一个简单的坐标并返回一个可读的街道地址。

首先,您需要添加一些 UI 以向用户显示地址。

打开Main.storyboard并添加一个UILabelMapViewController场景。确保将标签添加到MapViewController视图,而不是GMSMapView.

接下来,打开属性检查器,并为标签提供以下属性:

  • 对齐设置为center
  • 线设置为0。令人惊讶的是,这让标签可以根据需要占用尽可能多的行以适应文本。去搞清楚!:]
  • 背景设置为白色不透明度为 85%

完成后,标签的属性检查器和场景的对象树应如下所示:

19.png

20.png

最后给label的左、下、右约束添加0,如下图:

21.png

这会将标签固定到屏幕底部并将其拉伸到整个屏幕宽度。

您的故事板场景应如下所示:

22.png

接下来,为标签创建一个出口。选择Assistant Editor,然后ctrl+从 Document Outline 中的标签拖动到MapViewController.swift。将连接类型设置为Outlet,将名称设置为addressLabel并单击Connect

这会为您添加一个MapViewController可以在代码中使用的属性:

@IBOutlet 弱 变量地址标签:UILabel!

将以下方法添加到MapViewController

私人 函数 reverseGeocodeCoordinate(_ 坐标:CLLocationCoordinate2D){
    
  // 1
  让geocoder =  GMSGeocoder ()
    
  // 2 
  geocoder.reverseGeocodeCoordinate(coordinate) { response, error in 
    guard  let address = response ? .firstResult(), let lines = address.lines else {
       return
    }
      
    // 3 
    self .addressLabel.text = lines.joined(separator: " \n " )
      
    // 4 
    UIView .animate(withDuration: 0.25 ) {
       self .view.layoutIfNeeded()
    }
  }
}

再一次,这是每个评论部分的作用:

  1. 创建一个GMSGeocoder对象以将经纬度坐标转换为街道地址。
  2. 要求地理编码器对传递给方法的坐标进行反向地理编码。然后它验证 type 的响应中有一个地址GMSAddress。这是一个模型类,用于返回的地址GMSGeocoder
  3. 将 的文本设置为addressLabel地理编码器返回的地址。
  4. 设置地址后,为标签的固有内容大小的变化设置动画。

每次用户更改他们在地图上的位置时,您都需要调用此方法。为此,您将使用GMSMapViewDelegate.

在MapViewController.swift的底部添加另一个扩展,如下所示:

// MARK: - GMSMapViewDelegate
扩展 MapViewController : GMSMapViewDelegate {
  
}

这将声明MapViewController符合GMSMapViewDelegate协议。

接下来,将以下代码行添加到viewDidLoad()

mapView.delegate = 自我

这使得MapViewController地图视图的委托。

最后,将以下方法添加到新添加的扩展中:

func  mapView(_mapView :GMSMapView,idleAt位置:GMSCameraPosition ) { 
  reverseGeocodeCoordinate(位置.目标)
}

每次地图停止移动并固定在新位置时都会调用此方法,然后您可以调用该方法对新位置进行反向地理编码并更新addressLabel的文本。

构建并运行您的应用程序;你会看到你当前位置的地址——真实的或模拟的——在屏幕底部弹出:

23.png

注意到这张图片有什么问题吗?

幸运的是,GMSMapView为此提供了一个非常简单的解决方案:填充。当填充应用于地图时,所有视觉元素都将根据该填充放置。

回到reverseGeocodeCoordinate(_:),并在动画块之前添加这些行:

// 1 
let labelHeight =  self .addressLabel.intrinsicContentSize.height
 self .mapView.padding =  UIEdgeInsets (top: self .view.safeAreaInsets.top, left: 0 ,
                                    底部:labelHeight,右侧:0

现在,将动画块替换为:

UIView .animate(withDuration: 0.25 ) {
   //2 
  self .pinImageVerticalConstraint.constant = ((labelHeight -  self .view.safeAreaInsets.top) *  0.5 )
   self .view.layoutIfNeeded()
}

这些做两件事:

  1. 在动画块之前,这会在地图的顶部和底部添加填充。顶部填充等于视图的顶部安全区域插图,而底部填充等于标签的高度。
  2. 通过调整其垂直布局约束来更新定位销的位置以匹配地图的填充。

再次构建并运行您的应用程序;这一次,一旦标签可见,Google 徽标和定位按钮将移动到新位置:

24.png

移动地图;您会注意到每次地图调整到新位置时地址都会发生变化。让我们添加一个视觉效果来抑制地址变化。

将以下方法添加到GMSMapViewDelegate扩展中:

func  mapView ( _mapView  : GMSMapView , willMove手势: Bool ) { 
  地址标签.lock()
}

每次地图开始移动时都会调用此方法。它会收到一个Bool告诉您移动是源自用户手势(例如滚动地图)还是源自代码的信息。你调用lock()onaddressLabel给它一个加载动画。

当有 时lock(),也必须有unlock()reverseGeocodeCoordinate(coordinate:)将以下内容添加到's 闭包的顶部:

自我.addressLabel.unlock()

注意:有关 and 的完整实现lock()unlock()请查看UIViewExtensions.swift

构建并运行您的应用程序;当您滚动地图时,您应该会在地址标签上看到加载动画,如下所示:

25.png

找东西吃

现在地图已经设置好了,并且你已经掌握了用户的位置,是时候让这个用户吃饱了!

您将使用Google Places API搜索用户所在位置周围的餐饮场所。Google Places API是一个免费的网络服务 API,您可以使用它来查询以查找任何给定点附近的机构、地理位置或其他兴趣点。

Google Maps iOS SDK 为您提供了GMSMarker在地图上标记位置的类。每个标记对象都包含一个坐标和一个图标图像,并在添加时呈现在地图上。

对于这个应用程序,您需要有关每个标记的更多信息,因此您需要创建一个GMSMarker.

创建一个新的Cocoa Touch 类,命名它PlaceMarker并使其成为GMSMarker. 确保选择Swift作为此文件的语言。

将PlaceMarker.swift的内容替换为以下内容:

导入UIKit
导入GoogleMaps

class  PlaceMarker : GMSMarker {
   // 1 
  let place: GooglePlace
  
  // 2 
  init ( place : GooglePlace ) {
     self .place = place
     super . 初始化()
    
    位置=地点.坐标
    icon =  UIImage (命名:place.placeType + "_pin" )
    groundAnchor =  CGPoint (x: 0.5 , y: 1 )
    出现动画= .pop
  }
}

这是一段相对简单的代码:

  1. 将类型的属性添加GooglePlacePlaceMarker.
  2. 声明一个新的指定初始化器,它接受 aGooglePlace作为其唯一参数,并PlaceMarker使用位置、图标图像、标记位置的锚点和外观动画完全初始化 a。

接下来,向MapViewController.swift添加另外两个属性,如下所示:

private  let dataProvider =  GoogleDataProvider ()
 private  let searchRadius: Double  =  1000

您将使用dataProvider(在GoogleDataProvider.swift中定义)调用Google Places Web API,并searchRadius确定距离用户位置多远(以米为单位)来搜索地点。

将以下方法添加到MapViewController

private  func  fetchNearbyPlaces (坐标: CLLocationCoordinate2D ) {
   // 1
  mapView.clear()
  // 2 
  dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { 放置在
    地方.forEach {
      // 3 
      let marker =  PlaceMarker (place: $0 )
       // 4 
      marker.map =  self .mapView
    }
  }
}

让我们回顾一下您刚刚添加的内容:

  1. 清除所有标记的地图。
  2. 用于dataProvider向 Google 查询 中的附近地点searchRadius,过滤到用户选择的类型。
  3. 遍历完成闭包中返回的结果并PlaceMarker为每个结果创建一个。
  4. 设置标记的地图。这行代码告诉地图渲染标记。

这是 64,000 美元的问题:你想什么时候调用这个方法?

首先,用户可以合理地期望在应用启动时看到附近的地方。

locationManager(_:didUpdateLocations:)在末尾找到并添加以下代码行:

fetchNearbyPlaces(坐标:location.coordinate)

其次,用户可以更改要在地图上显示的地点类型,因此如果所选类型发生更改,您需要更新搜索结果。

找到typesController(_:didSelectTypes:)以下代码行并将其添加到末尾:

fetchNearbyPlaces(坐标:mapView.camera.target)

最后,您需要为用户提供在位置更改时获取新位置的选项。

打开Main.storyboard并将对象库中的a 拖到导航栏UIBarButtonItem的左侧。MapViewController将按钮的 Identifier 更改为Refresh,如下所示:

26.png

选择Assistant Editorctrl+Refresh按钮拖动到MapViewController.swift。选择Action并命名方法refreshPlaces。将以下代码插入新添加的方法中:

fetchNearbyPlaces(坐标:mapView.camera.target)

构建并运行您的项目;您会看到地图周围出现位置图钉。更改中的搜索类型TypesTableViewController并查看结果如何变化:

27.png

所有这些标记确实为地图添加了一些颜色,但如果没有额外的信息来为用户提供有关固定位置的一些详细信息,它们并没有多大用处。

将以下方法添加到MapViewController.swiftGMSMapViewDelegate的扩展中:

func  mapView(_mapView :GMSMapView,markerInfoContents标记:GMSMarker ) -> UIView?{
   // 1后卫let placeMarker = marker as? PlaceMarker else {
    返回零 
      
  }
    
  // 2
  后卫 let infoView =  UIView .viewFromNibName( "MarkerInfoView" ) as?  MarkerInfoView  else {
    返回 零
  }
    
  // 3 
  infoView.nameLabel.text = placeMarker.place.name
    
  // 4 
  if  let photo = placeMarker.place.photo {
    infoView.placePhoto.image =照片
  }其他{
    infoView.placePhoto.image =  UIImage(命名:“通用”)
  }
    
  返回信息查看
}

每次用户点击地图上的标记时都会调用此方法。如果您返回一个视图,那么它会在标记上方弹出。如果nil返回,则没有任何反应。这是怎么发生的?

  1. 您首先将点击的标记投射到PlaceMarker.
  2. MarkerInfoView接下来,您从其笔尖创建一个。该类MarkerInfoViewUIView本教程的入门项目附带的子类。
  3. 然后将地名应用到nameLabel.
  4. 检查是否有该地点的照片。如果是这样,将该照片添加到信息视图中。如果没有,请添加通用照片。

在运行应用程序之前,您需要确保位置图钉没有覆盖信息窗口。将以下方法添加到GMSMapViewDelegate扩展中:

func  mapView ( _mapView  : GMSMapView , didTap marker : GMSMarker ) -> Bool { 
  mapCenterPinImage.fadeOut( 0.25 )
  返回 false
}

此方法只是在点击标记时隐藏定位销。该方法返回false以指示您不想在点击标记时覆盖默认行为 - 以标记为中心使地图居中。

显然,该引脚需要在某个时候重新出现。将以下内容添加到末尾mapView(_:willMove:)

如果(手势){
  mapCenterPinImage.fadeIn( 0.25 )
  mapView.selectedMarker =  nil
}

这将检查移动是否源自用户手势;fadeIn(_:)如果是这样,它会使用该方法取消隐藏定位销。将地图设置selectedMarker为 nil 将删除当前呈现的infoView.

最后,将以下方法添加到GMSMapViewDelegate扩展中:

func  didTapMyLocationButton (用于 mapView : GMSMapView ) -> Bool {
  mapCenterPinImage.fadeIn( 0.25 )
  mapView.selectedMarker =  nil
  返回 false
}

该方法在用户点击定位按钮时运行;然后地图将以用户的位置为中心。再次返回false表示它不会覆盖点击按钮时的默认行为。

构建并运行您的应用程序;选择一个标记,您会看到位置图钉淡出。滚动地图会关闭infoView并带回图钉:

29.png

就是这样,你做到了!您现在拥有一个功能齐全的 Google 地图应用程序。:]

结论

请记住,在您能够运行这个已完成的项目之前,您需要插入您的 API 密钥,就像您在本教程开始时所做的那样。

我在这里的目的不是要让你相信 Google Maps iOS SDK 比 Apple 自己的 MapKit 更好;但是,以下是 Google 地图的一些优点和缺点:

优点

  • Google 对 SDK 的频繁更新。
  • 跨平台(iOS 和 Android)应用程序的统一体验。
  • 谷歌地图通常更详细,尤其是在美国以外。例如:

30.png

31.png

缺点

  • 与 Google Maps 不同,MapKit 是 iOS 原生的,这意味着它始终与 iOS 同步,并且开箱即可与 Swift 一起使用。
  • 与 MapKit 相比缺乏稳定性。
  • MapKit 与 CoreLocation 和 CoreAnimation 有更好的集成。

您可以使用教程中的所有代码部分下载最终项目。

这里也推荐一些面试相关的内容!