地图和定位功能的实现-篇幅略大,手机慎入 - 尕小天

时间:2024-03-10 20:24:58

地图和定位功能的实现-篇幅略大,手机慎入

来一起学习下地图和定位的使用吧,如有不足,欢迎指正
 
一.定位功能
1.ios7中的定位
 
     1.1 导入 CoreLocation框架
 
     1.2 创建 CLLocationManager对象
          注意:要用强指针指向这个对象,一般采用懒加载来创建     
1 private lazy var mgr : CLLocationManager = CLLocationManager()
 
     1.3 设置代理,实现代理方法
 
     1.4 开始定位
mgr.startUpdatingLocation()
    
     1.5 优点:不需要设置用户的授权
          在info.plist加上一个key   Privacy - Location Usage Description 写在value上的文字,可以显示在提示权限的文本框内
 
2.ios8(之后)的定位
 
     2.1 请求定位步骤
          2.11 导入CoreLocation框架
          2.12 懒加载CLLocationManager对象
          2.13 请求授权 (1) whenInUse (2) always
          2.14 注意:必须把授权对应的key值 添加到info.plist文件中
          2.15 设置代理,实现代理方法
          2.16 开始定位
 
     2.2 定位属性的应用
          2.21 精确度的使用
               desiredAccuracy精确度越高,越耗电
               属性接收double类型的值,不过最好传系统给定好的值
         kCLLocationAccuracyBestForNavigation: 导航精确度(最精确)
         kCLLocationAccuracyBest: 最好精确度(默认)
         kCLLocationAccuracyNearestTenMeters: 10米的误差
         kCLLocationAccuracyHundredMeters: 100米的误差
         kCLLocationAccuracyKilometer: 千米误差
         kCLLocationAccuracyThreeKilometers: 三千米的误差 
mgr.desiredAccuracy = kCLLocationAccuracyBestForNavigation
 
          2.22 移动一段距离,再次重新定位
           设置用于移动多少距离,重新进行定位         
mgr.distanceFilter = 100
 
     2.3 位置信息的获取
          2.31 发送完请求定位,怎么获取位置信息?
               在代理方法的闭包中,有一个数组,返回了很多信息在里面
     
          2.32 我们常用的信息就是经纬度
 
 
二.计算两个经纬度的距离
 
1.获取当前位置信息
     1.1 导入框架
     1.2 懒加载管理者对象
     1.3 请求授权
     1.4 添加key值
     1.5 设置代理,实现代理方法
     1.6 开始定位     
 
2.获取另一个位置的经纬度
 
3.计算两个位置的距离   distanceFromLocation
 
 
三.简易指南针的制作
 
1.实现思路
     监听手机头方向的改变,在手机屏幕上放一张图片,始终指向北(根据手机方向的改变旋转)
 
2.界面搭建
     拖一个UIImageView放在屏幕*,里面放一张图片
 
3.监听手机头方向的改变
     3.1 怎么监听?
          通过发送请求(定位服务),获取手机头的方向进行监听
 
     3.2 具体实现 
          3.21 导入CoreLocation框架
          3.22 懒加载CLLocationManager对象
          3.23 请求授权 (1) whenInUse (2) always
          3.24 注意:必须把授权对应的key值 添加到info.plist文件中
          3.25 设置代理,实现代理方法
          3.26 请求手机头方向
          3.27 获取真北方向
          3.28 将真北方向转换为弧度
          3.29 让图片根据弧度进行旋转(注意:图片旋转的弧度要取反 ,  要和屏幕旋转方向相反才能保持一直指向一个方向)
 
 
4.对指南针优化
     4.1 真实的指南针指向一个位置,会来回摆动两下才固定位置
          代码实现的指南针没有这个效果
     
     4.2 如何实现这个效果?
          可以通过一个动画来实现 
    // Damping : 阻力系数 (0~1.0)      initialSpringVelocity:回弹速度
        UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 5.0, options: [], animations: {
            self.imageView.transform = transform
            }, completion: nil)
 
四.区域监听
 
1.需求,当进入某指向区域,提醒用户进入该区域,离开该区域也对用户进行提醒
 
2.实现方案步骤
     2.1 懒加载 CLLocationManager对象
     2.2 请求授权(注意:必须使用alyays授权方式) ,配置info文件
     2.3 设置代理
     2.4 创建监听区域
     2.5 实现代理方法 并 开始监听
 
 
3.注意点
     3.1 当之前添加过监听区域时,再次添加新的监听区域,还会对之前的区域进行监听
     3.2 不想监听之前的区域,必须通过代码移除之前的区域
 
五.地理编码&反地理编码
 
需求:输入地理名称,地理编码获得该位置的经纬度.   输入经纬度,输出对应位置的地理名称
 
1.界面搭建
    1.1  整个界面放在屏幕的中心,怎么实现?
          1.11 可以用view包装 
               优缺点:要做大量的约束  , 但可以应用于任何版本
 
          1.12 ios9之后可以用UIStackView来包装
               优缺点:布局简便   只能适用于ios9(之后)
 
     
2.地理编码
     2.1 拿到用户输入的地理名称 (导入框架CoreLocation)
     2.2 地理编码
          2.21 创建 CLGeocoder对象
 
          2.22 对地理名称进行地理编码
     geocoder.geocodeAddressString(address) { (<#[CLPlacemark]?#>, <#NSError?#>) in
            <#code#>
        }
 
          2.23 对闭包中的CLPlacemark数据就行解析(遍历)
               一个地理名称可能对应多个地方,所以编码后的到的结果是一个数组
     
          2.24 获取数组中元素的地理位置(经纬度)
 
          2.25 将经纬度显示到界面
 
3.反地理编码
     3.1 拿到用户输入的经纬度
     3.2 对经纬度进行反地理编码
          3.21 创建 CLGeocoder对象
          
          3.22 对经纬度进行反地理编码
     geocoder.reverseGeocodeLocation(location) { (<#[CLPlacemark]?#>, <#NSError?#>) in
            <#code#>
        }
 
          3.23 对闭包中的CLPlacemark数据就行解析(遍历)
               一个经纬度可能对应多个位置(苹果这么设计的)  所以编码后返回一个数组
               一个位置包含多个信息(省/市/街道/国家/经纬度/)  编码后的结果是字典数组
 
          3.24 取出数组中的一个位置(字典),再获取位置信息(取出字典的元素)
               
          3.25 把获取到的地理名称显示到界面
 
地理编码&反地理编码源代码
 1 class ViewController: UIViewController {
 2    
 3     // MARK:- 控件属性
 4     @IBOutlet weak var addressTextView: UITextView!
 5     @IBOutlet weak var latitudeTextField: UITextField!
 6     @IBOutlet weak var longitudeTextField: UITextField!
 7     // MARK:- 懒加载属性
 8     private lazy var geocoder : CLGeocoder = CLGeocoder()
 9 }
10 
11 // MARK:- 地理编码
12 extension ViewController {
13     @IBAction func geocode() {
14        
15         // 1.获取用户输入的地址名称
16         guard let address = addressTextView.text else {
17             return
18         }
19         // 2.对地理名称进行地理编码
20         geocoder.geocodeAddressString(address) { (placemarks : [CLPlacemark]?, error : NSError?) in
21             // 1.错误校验
22             if error != nil {
23                 print(error)
24                 return
25             }
26             // 2.对结果进行校验
27             guard let placemarks = placemarks else {
28                 return
29             }
30             // 3.遍历所有的结果
31             for place in placemarks {
32                 print(place.name)
33                
34                 // 获取地理位置
35                 guard let location = place.location else {
36                     continue
37                 }
38                
39                 // 获取经纬度
40                 let latitude = location.coordinate.latitude
41                 let longitude = location.coordinate.longitude
42                
43                 // 将经纬度显示textField中
44                 self.latitudeTextField.text = "\(latitude)"
45                 self.longitudeTextField.text = "\(longitude)"
46             }
47         }
48     }
49 }
50 
51 
52 // MARK:- 反地理编码
53 extension ViewController {
54     @IBAction func reverseGeocode() {
55         // 1.获取用户输入的经纬度
56         guard let latitude = latitudeTextField.text, let longitude = longitudeTextField.text else {
57             return
58         }
59        
60         // 2.将经纬度转成CLLocation对象
61         guard let latitudeD = Double(latitude), let longitudeD = Double(longitude) else {
62             return
63         }
64         let location = CLLocation(latitude: latitudeD, longitude: longitudeD)
65        
66         // 3.反地理编码
67         geocoder.reverseGeocodeLocation(location) { (placemarks : [CLPlacemark]?, error : NSError?) in
68             // 1.错误校验
69             if error != nil {
70                 print(error)
71                 return
72             }
73            
74             // 2.对结果进行校验
75             guard let placemarks = placemarks else {
76                 return
77             }
78             // 3.遍历结果
79             for place in placemarks {
80                 guard let addressDict = place.addressDictionary else {
81                     continue
82                 }
83                
84                 guard let addressArray = addressDict["FormattedAddressLines"] as? [String] else {
85                     continue
86                 }
87                
88                 guard let address = addressArray.last else {
89                     continue
90                 }
91                
92                 self.addressTextView.text = address
93             }
94         }
95     }
96 }
 
六.把定位封装为工具类
 
1.将工具类设计成单例对象
 
2.封装请求方法,在方法中传入闭包
     2.1 使用属性将闭包保存起来. (因为在代理方法才能拿到位置信息)
     2.2 请求用户位置(1.懒加载管理者对象,并在对象中直接设置请求授权和代理)
 
3.在代理方法中获得用户位置信息,并赋值给闭包属性
 
4.停止请求用户的位置
     mgr.stopUpdatingLocation()
 
5.当第一次发送请求位置信息,会返回多次位置信息,怎么解决这个问题?
     用户只需要定义一个Bool属性,对属性进行判断,为true就接收返回的位置信息
 
七.使用第三方框架请求位置信息
 
去github搜索LocationManager 找到框架去使用
一般用oc版的,swift也能用
 
 
*********************地图篇*************************
 
一.地图的基本展示
 
1.地图可以用一个MapView控件来展示
     注意:要导入MKMapKit框架
 
2.地图的展示类型,可以通过属性 mapType设置
     地图分为:标准地图,卫星地图,混合地图   ios9之后新出了: 三维混合/三维卫星地图
 
3.地图上可以展示哪些的内容
     比例尺,指南针,交通状况,标志建筑,显示用户位置(后面详细介绍)
 
4.可以对地图进行哪些操作
     缩放:zoomEnabled
     旋转:rotateEnabled
     滚动:scrollEnabled
     
 
二.显示用户的位置
 
1.怎么显示用户的位置?
     1.1 设置地图的一个属性即可mapView.showsUserLocation = true  或 mapView.userTrackingMode = .FollowWithHeading/. Follow
     1.2 注意:一定要设置请求授权
          1.21 创建 CLLocationManager对象
          1.22 调用方法授权  requestWhenInUseAuthorization 或 always
          1.23 在info文件中添加对应的key值
          
2.获取用户的位置
     2.1 设置地图的代理
     2.2 实现代理方法
          在代理方法中通过userLocation.location?.coordinate 拿到经纬度
 
3.跟踪用户的位置
     3.1 首先要获取用户的位置
     3.2 设置属性即可  
mapView.userTrackingMode = .FollowWithHeading/. Follow
 
4.设置地图的显示区域
     4.1 通过一个属性就可以设置(一般在代理方法中设置)
mapView.setRegion(<#T##region: MKCoordinateRegion##MKCoordinateRegion#>, animated: <#T##Bool#>)
     
     4.2 需要传入MKCoordinateRegion参数,那么就需要创建这个参数    
MKCoordinateRegion(center: <#T##CLLocationCoordinate2D#>, span: <#T##MKCoordinateSpan#>)
    
     4.3 创建MKCoordinateRegion又需要传入CLLocationCoordinate2D和MKCoordinateSpan参数
 
     4.4 创建CLLocationCoordinate2D参数(经纬度)  可以在代理方法中获得
 
     4.5 创建MKCoordinateSpan参数   1纬度 = 111km     
 let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
          

 

5.点击按钮,回到用户的位置
     5.1 当用户拖动地图时,想让地图回到自己的位置,如果还需要拖动回来就太麻烦了
          可以设置一个按钮:点击按钮,立刻让地图的中心点就是自己的位置
 
     5.2 怎么实现?
          获取用户的位置(经纬度coordinate) ,将这个位置设置为地图的中心点即可
 
 
三.在地图上展示大头针
 
1.添加大头针
     1.1 创建大头针对象 addAnnotation(annotation: MKAnnotation)
          1.11 需要创建一个MKAnnotation
          1.12 进去头文件,发现MKAnnotation 是一个协议, 也就是需要传一个遵守该协议的对象
          1.13 自定义一个模型,遵守协议 , 协议里面只有三个计算属性
               也就是说,只需要实现这三个属性(在模型中定义这三个属性  注意:要定义为普通属性)
          1.14 创建模型对象
 
     1.2 将大头针对象添加到mapView中
     addAnnotation(annotation: MKAnnotation)
     
 
2.点击屏幕,在点击位置添加大头针
     2.1 获取用户点击的位置
     2.2 将点击的点转成经纬度
     2.3 根据经纬度创建大头针模型
     2.4 将大头针模型添加到mapView中
     
     
3.自定义大头针(修改大头针的子类)
     3.1 系统给定的大头针样式单一,我们想要其它样式的大头针,需要自定义大头针
 
     3.2 怎么自定义大头针?
          大头针能添加到view上,一定是一个控件,只要拿到这个控件,就可以进行修改
 
     3.3 怎么拿到大头针的view?
          在代理方法中会把大头针添加到mapView上,这个时候就可以拿到
 
     3.4 修改完大头针,发现点击大头针看不到title和subTitle了  为什么?
          需要设置一个属性才可以看到annoView?.canShowCallout = true
 
     3.5 设置大头针的样式,发现标记用户位置的图标也变为了大头针,  不想让标记位置的图标变为大头针,怎么办?
          判断大头针是否为用户值得大头针 MKUserLocation是的话就返回nil  (返回nil就是系统默认的大头针样式)
 
     3.6 对大头针进行性能优化(重用)
          和设置tableView的重用步骤差不多
  
 
4.自定义大头针(修改大头针,自定义大头针的image)
     
     4.1 系统自带的大头针只能显示一个大头针,我们想让大头针显示图片,只能自定义
          新建一个类,继承自 MKAnnotationView  用的时候,直接创建这个类即可
     
     4.2 怎么设置大头针显示的图片?
          只需要设置Image属性即可
 
     4.3 如何让大头针显示不同的图片?
           对大头针类型进行判断 ,给不同类型大头针设置不同的图片即可
 
      4.4 大头针非常多,判断的话很麻烦,也没技术含量,怎么解决?
           给大头针对象添加一个属性: iconName 属性里面保存对应的照片名称即可
           设置的时候,只需要取出属性的值,设置给UIImageView即可
     
     4.5 想在title左右两边也显示图片,怎么办?
           设置两个属性即可 leftCalloutAccessoryView     rightCalloutAccessoryView     
 
 
5.代码重构
     5.1 为什么要进行代码重构?
          把自定义大头针的操作全部写在控制器中,控制器太臃肿
     
     5.2 怎么对控制器进行”瘦身"
          把自定义大头针的代码抽取到一个view中
 
     5.3 怎么抽取?
          自定义大头针的view ,把相关代码封装到view里面
 
     5.4 抽取代码要用到模型和mapView怎么办?
          在自定义view中定义模型属性   把mapView当成参数传进去
 
     5.5 注意: 父类中已经存在这个模型属性了,在子类中不允许重复定义,怎么办?
          重复定义属性的时候,重写属性监听器方法即可
      
6.给大头针添加动画
     6.1 系统自带的大头针可以设置坠落动画,自定义的大头针怎么设置动画?
          我们只要拿到大头针view的frame就能实现坠落动画
 
     6.2 怎么拿到大头针的frame
          只需要获取大头针的view即可
 
     6.3 在代理方法中可以拿到view
 
     6.4 执行动画步骤
          6.41 保存大头针的y值
          6.42 设置大头针的y值为0
          6.43 再设置大头针的y值为原来的值,并执行动画
 
 
四.实现导航功能(了解)
 
1.利用系统的地图实现导航
     1.1 在自己app中打开系统地图,实现导航   
openMapsWithItems(mapItems: [MKMapItem], launchOptions: [String : AnyObject]?) -> Bool
     
     1.2 需要在方法中传入一个数组[MKMapItem](起点,终点) 和一个字典 [String : AnyObject] (导航的参数:驾车还是步行等)
     
     1.3 创建MKMapItem类型的起点和终点
 
     1.4 起点通过的一个方法就能实现,终点要利用地理编码获得
 1           @IBAction func startNavigating() {
 2         // 1.获取用户输入的地址
 3         guard let address = destinationTextField.text else {
 4             return
 5         }
 6         // 2.地理编码
 7         geocoder.geocodeAddressString(address) { (placemarks : [CLPlacemark]?, error : NSError?) in
 8             // 3.对错误进行校验
 9             if error != nil {
10                 return
11             }
12             // 4.获取placemark
13             guard let clpm = placemarks?.first else {
14                 return
15             }
16             // 5.创建终点的item
17             let mkpl = MKPlacemark(placemark: clpm)
18             let destinationItem = MKMapItem(placemark: mkpl)
19            
20             // 6.获取起点的item
21             let sourceItem = MKMapItem.mapItemForCurrentLocation()
22            
23             // 7.调用对应的导航方法
24             self.startNavigationWithSoureItem(sourceItem, destionationItem: destinationItem)
25         }
26     }
27    
28     private func startNavigationWithSoureItem(soureItem : MKMapItem, destionationItem : MKMapItem) {
29         // 1.获取起点和终点的item,并且放入到数组中
30         let items = [soureItem, destionationItem]
31         // 2.设置导航的参数
32         /*
33          MKLaunchOptionsDirectionsModeKey: 步行/驾车
34          MKLaunchOptionsMapTypeKey: 地图类型
35          MKLaunchOptionsShowsTrafficKey: 是否显示交通状况
36         */
37         var launchOptions = [String : NSObject]()
38         launchOptions[MKLaunchOptionsDirectionsModeKey] = MKLaunchOptionsDirectionsModeDriving
39         launchOptions[MKLaunchOptionsMapTypeKey] = MKMapType.Hybrid.rawValue
40         launchOptions[MKLaunchOptionsShowsTrafficKey] = true
41        
42         // 3.开始导航
43         MKMapItem.openMapsWithItems(items, launchOptions: launchOptions)
44     }
45 }
 
2.请求整个导航线路,在自己app中把线路画出来
     2.1 可以通过 MKDirections对象的一个方法实现
 
     2.2 首先要创建 MKDirections对象
 MKDirections(request: <#T##MKDirectionsRequest#>)
 
     2.3 还需要创建MKDirectionsRequest对象
 
     2.4 创建MKDirectionsRequest对象,并设置属性(起点位置和终点位置)
 
     2.5 通过MKDirections对象方法请求 calculateDirectionsWithCompletionHandler路线
 
     2.6 获取所有路线,并把路线通过 addOverlay方法添加到mapView上

 

五.集成百度地图
 
1.如何使用第三方SDK
     1.1 搜索想要集成的SDK
     1.2 进入官方下载SDK开发包
     1.3 查看demo程序(运行看看有哪些功能) 
     1.4 根据API一步步集成(官方一般有文档教程)
 
2.基本集成
     2.1 先将需要集成的功能的框架导入到项目
     2.2 创建桥接文件(根据需求创建, 只有开发包是oc  自己代码是swift才需要创建)
     2.3 在桥接文件中导入头文件(官方文档一般会给需要导入的头文件)
     2.4 配置桥接文件
     
3.请求授权
   3.1 如何授权?
          在AppDelegate文件中进行授权
 
     3.2 授权完,运行直接报错,为什么?
          百度一些框架依赖系统的一些框架,还需要导入系统的一些框架
 
4. BMKMapView的展示
     4.1 创建 BMKMapView对象
     4.2 设置frame,添加到屏幕上
 
5.定位功能的实现
     通过代理就可以实现
 
6.POI检索功能(查找功能)
     注意:发起检索一定要在地图添加到view上之后