在 Swift 中使用百度地图 SDK
写在前面
百度地图 SDK提供了一套功能很强大的地图框架使用接口,它不仅提供构建地图的基本接口,还提供POI搜索、地理编码、路线规划、定位、本地覆盖物绘制等服务。而由于百度地图SDK官方网站 上给出的使用说明是使用 Objective-C 语言以及 Xcode 4来进行开发的,很多朋友在使用 Swift 语言来进行开发的过程中遇到了不少的问题,在此我将我的使用经验写下来给大家进行分享,帮助大家快速使用百度地图。
百度地图更新说明
2.8.1
7月16日,百度地图SDK发布了2.8.1版本的SDK,增加了不少新功能。
在这个版本中,百度地图不再提供 .a 静态库形式的SDK包,只提供 framework 静态库形式的SDK包。
此外,还增加了对 iOS 9 的支持。
2.7.0
4月9日,百度地图SDK发布了2.7.0版本的SDK,增加了不少新功能。
在这个版本当中,最重大的变更就是提供了framework静态库的SDK包,使百度地图 SDK与Swift的交互更加的友好和方便。
2.6.0
对于早先使用 iOS SDK 2.5.0以前童鞋来说,百度地图2.6.0目前做出了如下的更新,请大家要注意,不要被这几个坑坑到了。
从2.6.0开始,用户位置更新功能由原来的didUpdateUserLocation变更为了didUpdateBMKUserLocation,需要修改这个方法才能使用定位功能的用户位置更新。
从2.6.0开始,用户位置更新将由 BMKLocationService()类来完成,因此需要调用其startUserLocationService()方法才能够开始定位操作。
初始项目配置
从百度地图SDK 下载页面下载最新版本的 SDK 后(下载地址,目前最新版本为2.8.1)并且申请完密钥后(申请密钥教程),就可以创建一个新的项目来为项目配置百度地图 SDK 了。
拷贝文件到工程
使用2.8.0以前版本的 .a 静态库
首先将百度MapAPI提供的头文件(inc文件夹)和静态库(.a)文件拷贝到工程目录下。当然如果为了省事,完全可以把 inc 文件夹、静态库文件以及mapapi.bundle包文件全部拷贝到工程目录下面。
这里的话静态库文件我都放置到了libs文件夹下面便于管理。如图所示:
项目目录
然后别忘了将这些文件夹拖进项目里面,弹出来的提示勾选上Copy if needed。
使用 2.7.0以后版本的 framework 静态库
首先将百度MapAPI所提供的框架(framework)文件拷贝到文件目录下。这里的话,我们在项目的根目录中新建了一个Resource文件夹用来存放工程中所需要用到的所有第三方框架,其中新建了BaiduMapSDK来存放SDK文件。
然后别忘了将这些文件夹拖进项目里面,弹出来的提示勾选上Copy if needed。
桥接头文件
接下来要在项目当中引入桥接头文件(BaiduMapTest-Bridging-Header.h)。添加OC桥接头文件的方法有很多,在此我就不就详细介绍了:
第一种方法:自行创建一个.mObjective-C 文件,然后XCode会提示您是否添加桥接头文件,选择确认后,桥接头文件便创建成功了,名为"\(您项目的名称)-Bridging-Header.h"。
添加桥接头文件
第二种方法:自行添加一个.h文件,然后在项目设置的Bulid Setting -> Swift Compiler - Code Generation -> Objective-C Bridging Header中添加这个头文件的路径。
Build Setting里面的OC桥接头文件设置
在这里我推荐大家使用第一种方法,至于原因稍后我会进行解释。
创建完毕后,在这个桥接头文件下面输入以下语句来引入对百度 API 头文件的引用。
// framework框架头文件导入方法,以下两种方法二选一
#import <BaiduMapAPI/BMapKit.h> //引入所有的头文件
#import <BaiduMapAPI/BMKMapView.h> //只引入所需的头文件
配置静态库编译方式
将之前我们创建的.m文件修改为.mm 文件,具体操作方法是选中.m文件,然后在界面右方的功能窗格中的文件检查器中进行修改。首先是将 Name栏中的.m文件修改为.mm,然后再将 Type由Objective-C Source修改为Objective-C++ Source。如图所示:
修改.m 文件
为什么要这样子做呢?百度已经有了完好的解释:
注:静态库中采用ObjectC++实现,因此需要您保证您工程中至少有一个.mm后缀的源文件(您可以将任意一个.m后缀的文件改名为.mm),或者在工程属性中指定编译方式,即将Xcode的Project -> Edit Active Target -> Build -> GCC4.2 - Language -> Compile Sources As设置为"Objective-C++"
但是为什么我们不修改编译方式呢?那是因为我们可能还会使用其它的第三方库。而如果这些第三方库不是采用 Objective-C++来进行实现的话,那么那个第三方库就会失效。因此,为了规避这个方法出现,我们采用修改为.mm文件是万无一失的。当然如果你不打算使用其他第三方库的话也可以采用修改编译方式。其在 Xcode 6当中的实际路径为:Build Settings -> Apple LLVM 6.0 - Language -> Compile Sources As。
环境配置
在 Xcode 中选择项目设置, 定位到Build Settings ->Linking -> Other Linker Flags,双击添加-ObjC标识符。如图所示:
添加-ObjC标识符
之所以使用这个标识符,和Objective-C的一个重要特性:类别(category)有关。由于Objective-C没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致在调用类别中的方法时,出现"selector not recognized",也就是找不到方法定义的错误。为了解决这个问题,引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来。
Framework静态库配置方式
如果我们下载的是framework静态库框架文件的话,那么配置过程如下:
引入框架
下载好的框架文件分为适用真机和适用模拟器的框架,分别存放在libs/Release-iphoneos文件夹和libs/Release-iphonesimulator文件夹下。我们可以根据我们进行调试的设备来选择合适的框架。
如果都需要在真机和模拟器上进行调试,那么就要使用 UNIX 命令lipo将这两个文件进行合并。
教大家一个非常简单的方法吧!我们将Release-iphoneos和Release-iphonesimulator这两个文件夹拖到你的系统根目录下面(一般是你的用户名,这个目录下面还拥有下载、图片等等目录,或者使用“前往”->“个人”,或者使用快捷键“Shift + Command + H”前往)。
然后打开“终端”应用,不出意外的话输入
lipo -create Release-iphoneos/BaiduMapAPI.framework/BaiduMapAPI Release-iphonesimulator/BaiduMapAPI.framework/BaiduMapAPI -output BaiduMapAPI
这段代码就可以成功创建一个新的BaiduMapAPI文件了。如图所示:
终端输入
合并成功
接下来,将这个文件替换掉Release-iphoneos或者iphonesimulator文件夹中的BaiduMapAPI文件即可。
在Xcode中选中工程,在 TARGETS -> Build Phases -> Link Binary With Libaries 中点击“+”按钮,在弹出的窗口中点击“Add Other”按钮,选择替换后的 BaiduMapAPI.framework 文件添加到工程中。如图所示:
导入framework框架
引入资源文件
百度地图将资源图片存放在了mapapi.bundle文件当中,因此我们需要将这个文件引入项目,否则基础地图不能够正常显示。
打开BaiduMapAPI.framework文件,然后将里面的mapapi.bundle文件导入到项目当中,记得勾选Copy items if needed选项。
.a静态库配置方式
如果我们下载的是.a静态库框架文件的话,那么配置过程如下:
引入静态库文件
百度地图 SDK 官方网站上面给出了三种引入静态库文件的方法,但是根据我的使用经验来看,最适合的方法就是第三种方法了。
第三种方式
设置静态库的链接路径,同样在项目设置中,定位到 Build Settings -> Search Paths -> Library Search Paths中添加静态库目录。一般情况下的设置是$(PROJECT_DIR)/libs/Release$(EFFECTIVE_PLATFORM_NAME)。$(PROJECT_DIR)是指项目的目录所在位置,$(EFFECTIVE_PLATFORM_NAME)代表当前配置是OS还是simulator。如图所示:
设置静态库的链接路径
第二种方式
第二种方式和“引入framework静态库”方式是类似的,都是使用lipo命令来合并文件,只不过这次合并的是.a文件。
然后打开“终端”应用,输入
lipo -create Release-iphoneos/libbaidumapapi.a Release-iphonesimulator/libbaidumapapi.a -output libbaidumapapi.a
这段代码就可以成功创建一个新的.a文件了。如图所示:
终端输入
合并成功!
引入系统framework
按照百度的官方说明,引入CoreLocation.framework和QuartzCore.framework、OpenGLES.framework、SystemConfiguration.framework、CoreGraphics.framework、Security.framework即可。引入方法也是定位到项目设置,然后General -> Linked Frameworks and Libraries中进行添加。
Info.plist 设置
定位功能提示
自 iOS 8开始,苹果要求定位功能需要提示用户是否是允许地理位置使用。因此,需要在info.plist里添加(以下二选一,两个都添加默认使用NSLocationWhenInUseUsageDescription):
NSLocationWhenInUseUsageDescription ,允许在前台使用时获取GPS的描述
NSLocationAlwaysUsageDescription ,允许永久使用GPS的描述
如图所示:
添加GPS描述
此外,在使用Xcode6进行SDK开发过程中,需要在info.plist中添加:Bundle display name ,且其值不能为空(Xcode6新建的项目没有此配置,若没有会造成manager start failed)。在这里我们添加$(PRODUCT_NAME),也就是项目名称。如图所示:
添加 Bundle display name
HTTPS配置
自 iOS 9 开始,苹果要求使用更安全的HTTPS协议来进行数据的传输,尽可能避免使用HTTP协议,因此,需要在info.plist中添加:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
如图所示:
感谢@JohnSmith的提醒
好了,百度地图SDK的初始配置就结束了,这时候您的项目应该要能够顺利的通过编译。如果编译出错,请重新检查配置是否正确。
初始化 BMKMapManager
在 AppDelegate.swift文件当中添加相关初始化代码,使得完成对 BMKMapManager 的初始化,具体解释都在代码当中:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mapManager: BMKMapManager?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
mapManager = BMKMapManager() // 初始化 BMKMapManager
// 如果要关注网络及授权验证事件,请设定generalDelegate参数
let ret = mapManager?.start("在此处输入您的授权Key", generalDelegate: nil) // 注意此时 ret 为 Bool? 类型
if !ret! { // 如果 ret 为 false,先在后面!强制拆包,再在前面!取反
NSLog("manager start failed!") // 这里推荐使用 NSLog,当然你使用 println 也是可以的
}
self.window?.addSubview(navigationController!.view) // 以下这两句如果不用 navigationController 的话完全可以不用要的
self.window?.makeKeyAndVisible()
return true
}
...
}
创建BMKMapView
定位到 ViewController.swift 文件,便可以创建BMKMapView 了。
实例代码如下:
class ViewController: UIViewController, BMKMapViewDelegate {
var mapView: BMKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
mapView = BMKMapView(frame: self.view.frame)
self.view = mapView
}
override func viewWillAppear(animated: Bool) {
mapView.viewWillAppear()
mapView.delegate = self // 此处记得不用的时候需要置nil,否则影响内存的释放
}
override func viewWillDisappear(animated: Bool) {
mapView.viewWillDisappear()
mapView.delegate = nil // 不用时,置nil
}
...
}
要注意,这个类要遵循 BMKMapViewDelegate协议,否则不能够使用代理设置。BMKMapView新增viewWillAppear、viewWillDisappear方法来控制BMKMapView的生命周期,并且在一个时刻只能有一个BMKMapView接受回调消息,因此在使用BMKMapView的viewController中需要在viewWillAppear、viewWillDisappear方法中调用BMKMapView的对应的方法,并处理代理。
编译并在模拟器上运行,效果如下图所示:
模拟器运行
定位功能
百度地图使用的定位功能实际上仍然是系统自带的定位功能,百度地图仅仅只是进行了封装。因此,大大减小了我们的使用难度。
首先我们仍然定位到 ViewController.swift 文件当中,首先给这个类遵循BMKLocationServiceDelegate协议。然后创建一个变量用来保存位置服务设置。具体代码如下:
class ViewController: UIViewController, BMKMapViewDelegate, BMKLocationServiceDelegate {
...
var locService: BMKLocationService!
override func viewDidLoad() {
...
// 设置定位精确度,默认:kCLLocationAccuracyBest
BMKLocationService.setLocationDesiredAccuracy(kCLLocationAccuracyBest)
//指定最小距离更新(米),默认:kCLDistanceFilterNone
BMKLocationService.setLocationDistanceFilter(10)
//初始化BMKLocationService
locService = BMKLocationService()
//启动LocationService
locService.startUserLocationService()
mapView.showsUserLocation = false
//设置位置跟踪态
mapView.userTrackingMode = BMKUserTrackingModeNone
//显示定位图层
mapView.showsUserLocation = true
}
//实现相关delegate 处理位置信息更新
//处理方向变更信息
func didUpdateUserHeading(userLocation: BMKUserLocation!) {
mapView.updateLocationData(userLocation)
}
//处理位置坐标更新
func didUpdateBMKUserLocation(userLocation: BMKUserLocation!) {
mapView.updateLocationData(userLocation)
}
override func viewWillAppear(animated: Bool) {
...
locService.delegate = self
}
override func viewWillDisappear(animated: Bool) {
...
locService.delegate = nil
}
...
}
设置位置精确度有很多可选值,大致上包括以下几条:
kCLLocationAccuracyBest:设备使用电池供电时候最高的精度
kCLLocationAccuracyBestForNavigation:导航情况下最高精度,一般要有外接电源时才能使用
kCLLocationAccuracyNearestTenMeters:十米误差范围
kCLLocationAccuracyHundredMeters:百米误差范围
kCLLocationAccuracyKilometer:千米误差范围
kCLLocationAccuracyThreeKilometers:三千米误差范围
设置位置跟踪态也有以下几种:
BMKUserTrackingModeNone:不进行用户位置跟踪
BMKUserTrackingModeFollow:跟踪用户位置
BMKUserTrackingModeFollowWithHeading:跟踪用户位置并且跟踪用户前进方向
编译运行后,出现界面如下:
提示是否允许使用定位
这就是我们之前在Info.plist文件中设置的那个内容了,这里我们选择 Allow,允许使用定位。
在 iOS模拟器中,要如何模拟所在位置呢?其实是非常简单的,定位到 Debug -> Location -> Custom Location,在弹出的对话框中输入目的位置的经纬度即可,如图所示。这里我输入的是天安门位置的经纬度(39'9076'', 116'391'')。
设置经纬度
设置完经纬度后,大家就能够在地图上看到模拟的地理位置了,以一个蓝色小圆点进行显示。
定位成功
此外,我们也可以使用调试区域的“区域模拟“来实现定位功能,如图所示。不过“区域模拟”所提供的模拟区域比较少,只存在一些经典的城市区域:
区域模拟
注意:
定位频率和定位精度并不应当越精确越好,需要视实际情况而定,因为越精确越耗性能,也就越费电。
使用完定位服务后如果不需要实时监控应该立即关闭定位服务以节省资源。
iOS8中不需要指定中心点,系统会默认会将当前位置设置为中心点并自动设置显示的区域范围。
结语
关于百度地图的 Swift 版本,我在 Github 上面发布了一个小的 Demo,基本上是参照百度官方所提供的 Demo,目前 Demo 版本仍在更新制作中,尽请期待~