一 地图和定位的简介
1 主要应用场景:
—-> 1.1 周边:找餐馆、找KTV、找电影院等等(大众点评, 美团网)
—-> 1.2 导航:根据用户设定的起点和终点,进行路线规划,并指引用户如何到达(百度地图, 高德地图)
二 实现该功能的必要条件
1. 在iOS开发中,要想加入定位和地图两大功能,必须基于2个框架进行开发
—-> 1.1 CoreLocation(该篇着重介绍)
—-> 1.2 MapKit (下篇做介绍)
三 框架的讲解思路
1 地理定位 : 定位用户所在的位置, 获取对应的经纬度或者海拔等信息
2 地理编码 :
—-> 2.1 地理编码 : 南昌市青山湖区顺外路—–>113.381048 , 23.138369(数字是随便写的) : 地理编码
—-> 2.2 反地理编码 : 113.381048 , 23.138369(数字是随便写的)—-> 南昌市青山湖区顺外路 : 反地理编码
3 区域监听 : 事先在APP内部通过代码,指定一个区域, 那么当用户进入或离开区域的时候, 我们都可以监听到.
四 该部分需要知道的专业术语
1 LBS(Location Based Service) : 基于位置的服务
2 SoLoMo :Social Local Mobile(索罗门):
—-> 2.1 社交化 :在APP里面加入一些社交元素
—-> 2.1 本地化 : 基于LBS的周边搜索, 周边签到等服务
—-> 2.3 移动化 :1>移动网 : 3G/4G网, 相对于有线/无线电脑网络 2>移动app : 相对于”桌面应用”
五 iOS8.0之前的定位(了解即可)
1 只需要了解的原因 :
—-> 1.1 iOS8.0之前的版本慢慢即将被淘汰, 不再做适配
—-> 1.2 只要iOS8.0以后的定位功能实现, 那么直接把代码跑到iOS8.0之前的设备上, 依然是可以运行的, 不需要做任何修改
—-> 1.3 因为系统版本和XCode版本原因, 暂时没法安装iOS8.0之前的模拟器进行测试
2 前台定位
—-> 2.1 包含头文件 import CoreLocation
—-> 2.2 创建位置管理者并且设置代理
private lazy var location : CLLocationManager = {
let location = CLLocationManager()
location.delegate = self
return location
}()
—-> 2.3 开始定位 : (这里设计点击屏幕就开始定位)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
location.startUpdatingLocation()
}
—-> 2.4 采用代理方法实现定位
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("定位到了")
manager.stopUpdatingLocation()
}
}
3 startUpdatingLocation的注意点
—-> 3.1 如果想要使用位置管理者来开始实现某一个功能:start
—-> 3.2 如果想停止这个功能 : stop
—-> 3.3 一旦调用了这个方法,就会不断的获取用户的位置信息
—-> 3.4 在ios6.0之后,如果想要获取用户的隐私(照片,通信),系统会主动弹框让用户授权
—-> 3.5 一旦用户选择了don’t allow 意味着再也无法获取用户的位置信息.除非用户到设置界面设置允许你的app来获取我的位置
—-> 3.6 为了提高用户点击允许的几率,一般是在plist里面配置一个key,并且写上一个为何获取这个位置的段子Privacy - Location Usage Description
4 后台定位
—-> 4.1 基于ios8.0之前的前台定位前提下,我们只需要做一下设置中的一种就可以
—-> 4.2 配置Xcode后台设置
—-> 4.3 在info.plist文件中配置
5 iOS8.0之前的后台定位常见错误
—-> 5.1 定位不到, 对应的代理方法不执行
———> 5.1.1 检查运行的模拟器是否是iOS8.0之前的系统版本
———> 5.1.2 检查模拟器是否设置位置数据
———> 5.1.3 确保代码无问题(一般都是代理没有设置,或者位置管理器对象是局部变量)
———> 5.1.4 可能是模拟器BUG, 请将模拟器位置设为None,然后再次设置数据; 或者,重置模拟器
六 ios8.0之后的定位
1 ios8.0之后的前台定位
—-> 1.1 同样包含头文件import CoreLocation
—-> 1.2 创建位置管理者
private lazy var location : CLLocationManager = {
let location = CLLocationManager()
location.delegate = self
location.requestWhenInUseAuthorization()
return location
}()
—-> 1.3 同样在代理方法中实现定位
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("定位到了")
}
}
—–> 1.4 点击屏幕开始定位
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
location.startUpdatingLocation()
}
—-> 1.5 注意点
——–> 5.1.1 在ios8.0之后,当需要获取用户的位置信息时,系统不再主动弹框让用户进行授权
——–> 5.1.2 如果想要获取用户的位置信息,需要主动请求授权
——–> 5.1.3 请求前台定位(默认情况下,只能在前台获取用户的位置信息)
——–> 5.1.4 一定要在info.plist中配置对应的key(点击进入requestWhenInUseAuthorization可以找到key值)
——–> 5.1.5 不要忘记做版本适配
2 常见错误(如iOS8.0之前的后台定位常见错误)
3 ios8.0之后的后台定位
—-> 3.1 后台定位方案一 : 在前台定位的基础上, 勾选后台模式location updates
—-> 3.2 达到的预想效果 : 当APP退到后台, 会出现一个蓝条, 不断提醒用户,正在定位
—-> 3.3 后台定位方案二 : 在定位时, 直接请求前后台定位授权, 并在info.plist文件中配置对应的key
—-> 3.4 达到的预想效果 : 无论是否勾选后台模式, 都可以获取位置信息. 而且无论前后台, 都不会出现蓝条
4 ios8.0前后台定位测试环境 :
—-> 4.1 XCode版本无要求
—-> 4.2 模拟器选择iOS8.0之后, iOS9.0之前的版本
七 ios9.0之后的定位
1 ios9.0前台定位
—-> 1.1 和iOS8.0之后一致,没有变化(只要注意相关设置即可)
2 ios9.0后台定位
—-> 2.1 后台定位方案一 : 在前台定位授权的基础上,如果勾选了后台模式location updates之后, 还需要额外设置属性allowsBackgroundLocationUpdates = true;
—-> 2.2 在后台获取用户信息的时候,依旧会在屏幕的顶部显示蓝条提醒用户app在时时的获取用户的位置信息
—-> 2.3 当点击了这个蓝条,会打开对应的app
—-> 2.4 后台定位方案二 : 直接请求前后台定位授权的情况, 和iOS8.0之后一致, 没有变化
3 注意 : 在前台定位授权的情况下,如果使用代码允许了后台定位,必须开启后台模式,否则程序会崩溃
—-> 3.1 错误信息
4 可能会遇到的测试问题
—-> 4.1 如果在测试的时候,获取不到位置,是模拟器的问题,因为模拟器是没有位置的
5 代码
—-> 5.1 导入头文件import CoreLocation
—-> 5.2 创建位置管理者
class ViewController: UIViewController {
private lazy var location : CLLocationManager = {
let location = CLLocationManager()
location.delegate = self
if #available(iOS 8.0, *){
location.requestWhenInUseAuthorization()
}
if #available(iOS 9.0, *){
location.allowsBackgroundLocationUpdates = true
}
return location
}()
—-> 5.3 代理方法
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("定位到了")
}
}
—-> 5.4 开始定位
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
八 监听用户授权状态
1 实现CLLocationManagerDelegate的代理方法
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
}
2 各个授权对应的含义 :
NotDetermined
Restricted
Denied
AuthorizedAlways
AuthorizedWhenInUse
3 代理方法中模拟用户选择的状态代码块
switch status {
case .NotDetermined :
print("用户未决定")
case .Restricted :
print("受限制")
case .Denied :
if CLLocationManager.locationServicesEnabled() {
print("用户正在拒绝")
if
let url = NSURL(string: UIApplicationOpenSettingsURLString)!
if UIApplication.sharedApplication().canOpenURL(url){
UIApplication.sharedApplication().openURL(url)
}
}else {
}
} else {
print("定位服务关闭,建议打开定位服务")
}
case .AuthorizedAlways :
print("前后台定位授权")
case .AuthorizedWhenInUse :
print("前台定位授权")
}
}
4 定位失败也会调用代理方法
//该方法定位失败的时候就会调用error失败信息 CLLocationManager位置管理者
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
}
5 可能会用到的额外参数
—-> 5.1 distanceFilter
location.distanceFilter = 100
—-> 5.2 desiredAccuracy
location.desiredAccuracy = kCLLocationAccuracyBest
6 开发经验
—-> 6.1 定位本身就非常耗电, 定位的精确度越高, 越耗电, 定位时间越长
—-> 6.2 为了省电, 尽量在满足需求的情况下低精确度
7 ios9.0新出的API
if
location.requestLocation()
}
—-> 7.1 根据精确度来进行定位,先从最低的精度开始定位,直到定位到最好的,之后返回
—-> 7.2 如果在定位的过程中超时了,那么会返回指定定位到的那个位置,并且只会执行一次
—-> 7.3 该方法不能和stopUpdatingLocation一起用
九 CLLocation对象的使用
1 属性详解 :
—-> 1.1 coordinate : 当前位置所在的经纬度数据(苹果官方解释 : Returns the coordinate of the current location.)
—-> 1.2 altitude : 海拔(苹果官方解释 : Returns the altitude of the location. Can be positive (above sea level) or negative (below sea level).)–> 可以是整数也可以是负数
—-> 1.3 speed : 当前速度 (苹果官方解释 : Returns the speed of the location in m/s. Negative if speed is invalid.)–> 负数是无效的
—-> 1.4 course : 航向(设备移动的方向, 值域范围:0.0 ~ 359.9, 正北方向为0.0)(苹果官方解释 :0.0 - 359.9 degrees, 0 being true North)
2 重要函数 : Returns the lateral distance between two locations.(返回横向两者的距离(物理距离))
func distanceFromLocation(location: CLLocation) -> CLLocationDistance
3 开发经验 :
—-> 3.1 使用之前, 务必判断数据是否有效 : 代码 (判断数据是否可用)
if location.horizontalAccuracy < 0 {return}
—-> 3.2 功能: 如果水平精确度小于零, 代表虽然可以获取位置对象, 但是数据错误, 不可用
4 实例演练 :打印当前用户的行走方向,偏离角度以及对于的行走距离(例如 : 北偏东 30度方向 移动了8米)
—-> 4.1 创建位置管理者
private lazy var location :CLLocationManager = {
var location = CLLocationManager()
location.delegate = self
if #available(iOS 8.0, *) {location.requestAlwaysAuthorization()}
location.desiredAccuracy = kCLLocationAccuracyBest
return location
}()
—-> 4.2 设置一个属性记录用户上一个位置
private var lastLocation : CLLocation?
—-> 4.3 实现代理方法
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else{return}
if location.horizontalAccuracy < 0 {return}
if location.course < 0 {return}
let angleStrs = ["北偏东","东偏南","南偏西","西偏北"]
let index = Int(location.course / 90)
var angleStr = angleStrs[index]
let angle = location.course % 90
if Int(index) == 0 {
let index = angleStr.startIndex.advancedBy(1)
angleStr = "正" + angleStr.substringToIndex(index)
}
let lastLoc = lastLocation ?? location
let distance = location.distanceFromLocation(lastLoc)
lastLocation = lastLoc
if Int(index) == 0 {
print("\(angleStr),移动了\(distance)米")
} else {
print("\(angleStr) \(angle)度,移动了\(distance)米")
}
print("定位到了")
}
—-> 4.4 进行相关配置(上面都已经说明了)
5 经验小结
—-> 5.1 定位的应用场景
——–> 5.1.1 导航
——–> 5.1.2 电商APP,获取用户所在城市(需要与(反)地理编码联合使用)
——–> 5.1.3 数据采集用户信息(例如,统计app使用分布)
——–> 5.1.4 查找周边(周边好友, 周边商家等等)
—-> 5.2 开发经验(由于定位非常耗电; 所以为了给用户省电, 你可以遵守以下小经验)
——–> 5.2.1 不需要获取用户位置时,一定要关闭定位服务:
——–> 5.2.2 如果可以,尽可能使用低精度的desiredAccuracy
——–> 5.2.3 如果是数据采集,(一般都是周期性的去轮询用户位置),在轮询期间一定要关闭定位
十 指南针(了解)
1 实现思路 :
—-> 1.1 利用”磁力计”传感器,获取设备朝向
—-> 1.2 根据设备朝向反向旋转”指南针”图片
2 代码实现
—-> 2.1 在storyboard中设置指南针图片,并且通过拖线的方式拿到该对象
@IBOutlet weak var compassImageView: UIImageView!
—-> 2.2 导入头文件
import CoreLocation
—-> 2.3 懒加载位置管理者
private lazy var location : CLLocationManager = {
var location = CLLocationManager()
location.delegate = self
return location
}()
—-> 2.4 实现代理方法
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
if newHeading.headingAccuracy < 0 {return}
let magneticHeading = newHeading.magneticHeading
let radian = CGFloat(magneticHeading / 180 * M_PI)
UIView.animateWithDuration(0.25) { () -> Void in
self.compassImageView.transform = CGAffineTransformMakeRotation(-radian)
}
}
}
—-> 2.5 判断设置是否可用
override func viewDidLoad() {
super.viewDidLoad()
if CLLocationManager.headingAvailable() {
location.startUpdatingHeading()
} else {
SVProgressHUD.showErrorWithStatus("指南针不可用,请更换手机")
}
}
3 注意事项
—-> 3.1 获取设备朝向前, 先判断”磁力计”是否可用
—-> 3.2 获取朝向信息前, 判断当前朝向信息是否有效
—-> 3.3 注意”设备朝向”与”航向”的区别
—-> 3.4 使用”磁力计”传感器获取设备朝向, 不需要请求用户授权
4 测试环境
—-> 4.1 XCode版本无要求(建议:XCode7.0+ 不需要开发者账号也可以进行真机调试)
—-> 4.2 必须要求真机设备(只有真机设备才有”磁力计”传感器)
十一 区域监听(了解)
1 概念 :
—-> 1.1 区域 : 就是指划定的一块地域范围(比如圆形区域, 则由区域中心, 和半径组成)
—-> 1.2 区域监听 : 是指,我们通过代码指定一个区域, 然后当用户持握设备进入或者离开指定区域, 我们都能监听到.
2 注意事项 :
—-> 2.1 想要做区域监听, 在iOS8.0之后, 必须请求位置授权
—-> 2.2 原因 : 区域监听的原理就是获取用户的位置, 然后在判断该位置是否在制定区域内, 所以会涉及到用户隐私(位置), 而在iOS8.0之后, 想要访问用户位置信息, 就需要主动请求授权;
3 代码 :
—-> 3.1 在storyboard中设置一个label,用来显示用户的区域状态
@IBOutlet weak var noticeLabel: UILabel!
—-> 3.2 懒加载位置管理者
private lazy var location : CLLocationManager = {
let location = CLLocationManager()
location.delegate = self
location.requestAlwaysAuthorization()
return location
}()
—-> 3.3 判断该区域是否可以被监听
override func viewDidLoad() {
super.viewDidLoad()
if CLLocationManager.isMonitoringAvailableForClass(CLCircularRegion) {
let center = CLLocationCoordinate2DMake(21.123, 123.456)
var radius : CLLocationDistance = 10.0
let identify = "肖锋区域"
if radius > location.maximumRegionMonitoringDistance {
radius = location.maximumRegionMonitoringDistance
}
let region = CLCircularRegion(center: center, radius: radius, identifier: identify)
location.requestStateForRegion(region)
}
}
—-> 3.4 代理方法实现
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
print("您已经离开该区域")
}
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("您已经进入该区域")
}
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
switch state {
case .Unknown:
noticeLabel.text = "不知道"
case .Inside:
noticeLabel.text = "进入"
case .Outside:
noticeLabel.text = "离开"
}
}
—-> 3.5 进行info.plist相关配置NSLocationAlwaysUsageDescription
—-> 3.6 模拟器配置 : 将模拟器调整到Debug–>Location –> None–> 将设置的区域填入精度和纬度中
4 常见问题
—-> 4.1 区域监听, 测试没有效果?
——–> 4.1.1 首先, 确定代码没有问题, 是否有请求授权;
——–> 4.1.2 其次, 尝试修改模拟器位置信息, 触发进入区域或离开区域的动作
——–> 4.1.3 最后, 如果模拟器出现BUG, 定位不到, 也会无法判定当前区域状态; 所以, 最后可以尝试重置模拟器.
十二 地理编码和反地理编码
1 概念 :
—-> 1.1 地理编码 : 是指根据地址关键字, 将其转换成为对应的经纬度等信息;
—-> 1.2 反地理编码 : 是指根据经纬度信息, 将其转换成为对应的省市区街道等信息;
2 具体实现步骤 :
—-> 2.1 导入CoreLocation框架以及对应的主头文件
—-> 2.2 创建CLGeocoder对象
—-> 2.3 根据地址关键字, 进行地理编码 (地理编码)
—-> 2.3 根据经纬度信息, 进行反地理编码 (反地理编码)
3 测试环境
—-> 3.1 必须连网
—-> 3.2 XCode版本不限
—-> 3.2 iOS模拟器系统版本不限
4 具体事例代码 :
—-> 4.1 storyboard中相关设置
—-> 4.2 导入头文件import CoreLocation
—-> 4.3 通过拖线的方式拿到storyboard中相关对象
@IBOutlet weak var addressTV: UITextView!
@IBOutlet weak var longitudeTF: UITextField!
@IBOutlet weak var latitudeTF: UITextField!
—-> 4.4 懒加载CLGeocoder对象
private lazy var geoc : CLGeocoder = {
return CLGeocoder()
}()
—-> 4.5 地理编码
@IBAction func geocodingBtnClick()
{
let address = addressTV.text
geoc.geocodeAddressString(address) { (clplas :[CLPlacemark]?,error : NSError?) -> Void in
if error == nil {
guard let clplas = clplas else {return}
guard let clpl = clplas.first else {return}
self.addressTV.text = clpl.name!
self.longitudeTF.text = "\(clpl.location!.coordinate.longitude)"
self.latitudeTF.text = "\(clpl.location!.coordinate.latitude)"
}
}
}
—-> 4.6 反地理编码
@IBAction func reverGeocodingBtnClick()
{
let latitude = CLLocationDegrees(self.latitudeTF.text!)
let longitude = CLLocationDegrees(self.longitudeTF.text!)
let location = CLLocation(latitude: latitude!, longitude: longitude!)
geoc.reverseGeocodeLocation(location) { (clplas : [CLPlacemark]?,error : NSError?) -> Void in
if error == nil {
guard let clplas = clplas else {return}
guard let clpl = clplas.first else {return}
self.addressTV.text = clpl.name!
self.longitudeTF.text = "\(clpl.location!.coordinate.longitude)"
self.latitudeTF.text = "\(clpl.location!.coordinate.latitude)"
}
}
}
5 常见问题 : 测试无数据?
—-> 5.1 首先, 检查是否有联网;
—-> 5.2 其次, 如果是反地理编码,可尝试更换经纬度再次尝试, 有的经纬度没有对应信息
6 该功能具体应用场景
—-> 6.1 发表说说/发微博时来自哪里
—-> 6.2 美团或者大众点评获取用户所在区域
7 定位 + 反地理编码运用(该部分我只讲代码实现写上,具体怎么实现的,大家应该一看就明白)
import UIKit
import CoreLocation
class ViewController: UIViewController {
private lazy var location : CLLocationManager = {
let location = CLLocationManager()
location.delegate = self
location.requestAlwaysAuthorization()
return location
}()
private lazy var geco : CLGeocoder = {
return CLGeocoder()
}()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
location.startUpdatingLocation()
}
}
extension ViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {return}
if location.horizontalAccuracy < 0 {return}
geco.reverseGeocodeLocation(location) {(clplas : [CLPlacemark]?,error : NSError?) -> Void in
if error == nil {
guard let clplas = clplas else {return}
guard let clpl : CLPlacemark = clplas.first else {return}
print(clpl.name!)
}
}
}
}
十三 使用给第三方框架进行定位
1 使用原因 : 因为使用CoreLocation框架进行获取用户位置信息, 是通过代理进行回调; 而第三方框架将”代理模拟”转换成为”block模式”; 使用起来比较方便, 而且额外增加了超时时间等功能.
2 框架名称 : locationManager
3 框架地址 : https://github.com/intuit/LocationManager
4 注意事项 : 一般集成第三方框架到项目中, 请先确保该框架没有问题, 然后再向项目中集成(导入的时候预先编译下)
5 代码 :
—-> 5.1 获取单例 :
let locationMan = INTULocationManager.sharedInstance()
—-> 5.2 获取一次用户信息
// 获取一次用户信息
let locationRequestID : INTULocationRequestID = locationMan.requestLocationWithDesiredAccuracy(.Room, timeout: 5, delayUntilAuthorized: false) { (location : CLLocation!, _,status : INTULocationStatus) -> Void in
//判断
if status == INTULocationStatus.Success {
print("获取到位置")
} else {
print(status.rawValue)
}
}
—-> 5.3 一直获取用户位置信息
let LocationRequestID: INTULocationRequestID = locationMan.subscribeToLocationUpdatesWithDesiredAccuracy(.Room) { (location : CLLocation!, _,status : INTULocationStatus) -> Void in
if status == INTULocationStatus.Success {
print("获取到位置")
} else {
print(status.rawValue)
}
}
6 其它具体实现请参照该框架对应的 readME
十四 总结
1 地图与定位,知识点还是相对比较简单的,功能也容易实现,不求能理解,只要能运用就行.开发过程中的情况我都写在里面了,希望大家还是抽时间看一下.
2 最后,大家如果觉得我写的博客还行,麻烦大家多多关照,有什么问题可直接给我留言,我一定尽可能的解答,谢谢!!!!