美文网首页iOS14开发
iOS14开发-定位与地图

iOS14开发-定位与地图

作者: YungFan | 来源:发表于2021-07-11 16:25 被阅读0次

    定位

    CoreLocation 是 iOS 中用于设备定位的框架。通过这个框架可以实现定位进而获取位置信息如经度、纬度、海拔信息等。

    模块与常见类

    • 定位所包含的类都在CoreLocation模块中,使用时必须导入。
    • CLLocationManager:定位管理器,可以理解为定位不能自己工作,需要有个类对它进行全过程管理。
    • CLLocationManagerDelegate:定位管理代理,不管是定位成功与失败,都会有相应的代理方法进行回调。
    • CLLocation:表示某个位置的地理信息,包含经纬度、海拔等。
    • CLPlacemark:位置信息,包含的信息如国家、城市、街道等。
    • CLGeocoder:地理编码。

    工作流程

    1. 创建CLLocationManager,设置代理并发起定位。
    2. 实现CLLocationManagerDelegate中定位成功和失败的代理方法。
    3. 在成功的代理方法中获取CLLocation对象并通过CLGeocoder进行反向地理编码获取对应的位置信息CLPlacemark
    4. 通过CLPlacemark获取具体的位置信息。

    权限

    授权对话框

    • 程序中调用requestWhenInUseAuthorization发起定位授权。
    • 程序中调用requestAlwaysAuthorization发起定位授权。

    前台定位

    • 需要在 Info.plist 中配置Privacy - Location When In Use Usage Description
    • 程序中调用requestWhenInUseAuthorization发起定位授权。
    • 弹出的授权对话框新增了精确位置开关,同时新增了小地图展示当前位置。

    后台定位

    • 需要勾选 Capabilities —> Background Modes —> Location updates
    • 程序中允许后台定位:locationManager.allowsBackgroundLocationUpdates = true
    • 此时授权分为 2 种情况:
      (1)Privacy - Location When In Use Usage Description + requestWhenInUseAuthorization:可以后台定位,但会在设备顶部出现蓝条(刘海屏设备会出现在左边刘海)。
      (2)Privacy - Location When In Use Usage Description + Privacy - Location Always and When In Use Usage Description + requestAlwaysAuthorization:可以后台定位,不会出现蓝条。这种方式会出现 2 次授权对话框:第一次和前台定位一样,在同意使用While Using App模式后,继续使用定位才会弹出第二次,询问是否切换到Always模式。

    精度控制

    • iOS 14 新增了一种定位精度控制,在定位授权对话框中有一个精度切换开关,可以切换精确和模糊定位(默认精确)。
    • 可以通过CLLocationManageraccuracyAuthorization属性获取当前的定位精度权限。
    • 当已经获得定位权限且当前用户选择的是模糊定位,则可以使用CLLocationManagerrequestTemporaryFullAccuracyAuthorization(withPurposeKey purposeKey: String, completion: ((Error?) -> Void)? = nil)方法申请一次临时精确定位权限,其中purposeKey为 Info.plist 中配置的Privacy - Location Temporary Usage Description Dictionary字段下某个具体原因的 key,可以设置多个 key 以应对不同的定位使用场景。
    • requestTemporaryFullAccuracyAuthorization方法并不能用于申请定位权限,只能用于从模糊定位升级为精确定位;如果没有获得定位权限,直接调用此 API 无效。
    • 如果不想使用精确定位,则可以在 Info.plist 中配置Privacy - Location Default Accuracy ReducedYES,此时申请定位权限的小地图中不再有精度切换开关。需要注意 2 点:
      (1)如果发现该字段不是 Bool 型,需要以源码形式打开 Info.plist,然后手动修改<key>NSLocationDefaultAccuracyReduced</key>为 Bool 型的值,否则无法生效。
      (2)配置该字段后,如果 Info.plist 中还配置了Privacy - Location Temporary Usage Description Dictionary,则仍可以通过requestTemporaryFullAccuracyAuthorization申请临时的精确定位权限,会再次弹出授权对话框进行确认。

    模拟器定位

    由于定位需要 GPS,一般情况下需要真机进行测试。但对于模拟器,也可以进行虚拟定位,主要有 3 种方式。

    • 方式一

      (1)新建一个gpx文件,可以取名XXX.gpx,然后将自己的定位信息填写进 xml 对应的位置。
      (2)gpx文件设置完成以后,首先需要运行一次 App,然后选择Edit Scheme,在Options中选择自己的gpx文件,这样模拟器运行的时候就会读取该文件的位置信息。然后可以选择Debug—>Simulate Location或底部调试栏上的定位按钮进行gpx文件或位置信息的切换。
    <?xml version="1.0"?>
    <gpx version="1.1" creator="Xcode">
        <!--安徽商贸职业技术学院 谷歌地球:31.2906511800,118.3623587000-->
        <wpt lat="31.2906511800" lon="118.3623587000">
            <name>安徽商贸职业技术学院</name>
            <cmt>中国安徽省芜湖市弋江区文昌西路24号 邮政编码: 241002</cmt>
            <desc>中国安徽省芜湖市弋江区文昌西路24号 邮政编码: 241002</desc>
        </wpt>
    </gpx>
    
    • 方式二:运行程序开始定位 —> 模拟器菜单 —> Features —> Location —> Custom Location —> 输入经纬度。

    实现步骤

    1. 导入CoreLocation模块。
    2. 创建CLLcationManager对象,设置参数和代理,配置 Info.plist 并请求定位授权。
    3. 调用CLLcationManager对象的startUpdatingLocation()requestLocation()方法进行定位。
    4. 实现代理方法,在定位成功的方法中进行位置信息的处理。
    import CoreLocation
    import UIKit
    
    class ViewController: UIViewController {
        // CLLocationManager
        lazy var locationManager = CLLocationManager()
        // CLGeocoder
        lazy var gecoder = CLGeocoder()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setupManager()
        }
    
        func setupManager() {
            // 默认情况下每当位置改变时LocationManager就调用一次代理。通过设置distanceFilter可以实现当位置改变超出一定范围时LocationManager才调用相应的代理方法。这样可以达到省电的目的。
            locationManager.distanceFilter = 300
            // 精度 比如为10 就会尽量达到10米以内的精度
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            // 代理
            locationManager.delegate = self
            // 第一种:能后台定位但是会在顶部出现大蓝条(打开后台定位的开关)
            // 允许后台定位
            locationManager.allowsBackgroundLocationUpdates = true
            locationManager.requestWhenInUseAuthorization()
            // 第二种:能后台定位并且不会出现大蓝条
            // locationManager.requestAlwaysAuthorization()
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            // 以下2个方法都会调用代理方法
            // 1. 发起位置更新(定位)会一直轮询,耗电
            locationManager.startUpdatingLocation()
            // 2. 只请求一次用户的位置,省电
            // locationManager.requestLocation()
        }
    }
    
    extension ViewController: CLLocationManagerDelegate {
        // 定位成功
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            if let location = locations.last {
                // 反地理编码转换成具体的地址
                gecoder.reverseGeocodeLocation(location) { placeMarks, _ in
                    // CLPlacemark -- 国家 城市 街道
                    if let placeMark = placeMarks?.first {
                        print(placeMark)
                        // print("\(placeMark.country!) -- \(placeMark.name!) -- \(placeMark.locality!)")
                    }
                }
            }
            // 停止位置更新
            locationManager.stopUpdatingLocation()
        }
    
        // 定位失败
        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print(error.localizedDescription)
        }
    }
    

    地图

    • 地图所包含的类都在MapKit模块中,使用时必须导入。
    • 除了可以显示地图,还支持在地图上进行标记处理。
    • 地图看似很复杂,其实它仅仅是一个控件 MKMapView,就和以前学习过的 UIButton、UITableView 等一样,可以在 storyboard 和代码中使用。
    • 地图上如果想要显示用户的位置,必须与定位配合,那么就需要创建定位管理器、设置权限等(参考定位知识),同时需要通过 storyboard 或者代码设置地图的相关属性。

    准备工作

    1. 添加一个地图并设置相关属性。
    2. Info.plist 中配置定位权限。
    3. 创建 CLLocationManager 对象并请求定位权限。

    基本使用

    显示地图,同时显示用户所处的位置。点击用户的位置,显示一个气泡展示用户位置的具体信息。

    import MapKit
    
    class ViewController: UIViewController {
        @IBOutlet var mapView: MKMapView!
        lazy var locationManager: CLLocationManager = CLLocationManager()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setupMapView()
        }
    
        func setupManager() {
            locationManager.requestWhenInUseAuthorization()
            // 不需要发起定位
        }
    
        func setupMapView() {
            // 设置定位
            setupManager()
            // 地图类型
            mapView.mapType = .hybridFlyover
            // 显示兴趣点
            mapView.showsPointsOfInterest = true
            // 显示指南针
            mapView.showsCompass = true
            // 显示交通
            mapView.showsTraffic = true
            // 显示建筑
            mapView.showsBuildings = true
            // 显示级别
            mapView.showsScale = true
            // 用户跟踪模式
            mapView.userTrackingMode = .followWithHeading
        }
    }
    

    缩放级别

    在之前功能的基础上实现地图的任意视角(“缩放级别”)。

    // 设置“缩放级别”
    func setRegion() {
        if let location = location {
            // 设置范围,显示地图的哪一部分以及显示的范围大小
            let region = MKCoordinateRegion(center: mapView.userLocation.coordinate, latitudinalMeters: 500, longitudinalMeters: 500)
            // 调整范围
            let adjustedRegion = mapView.regionThatFits(region)
            // 地图显示范围
            mapView.setRegion(adjustedRegion, animated: true)
        }
    }
    

    标注

    在地图上可以添加标注来显示一个个关键的信息点,用于对用户的提示。

    分类

    • MKPinAnnotationView:系统自带的标注,继承于 MKAnnotationView,形状跟棒棒糖类似,可以设置糖的颜色,和显示的时候是否有动画效果 (Swift 不推荐使用)。
    • MKMarkerAnnotationView:iOS 11 推出,建议使用。
    • MKAnnotationView:可以用指定的图片作为标注的样式,但显示的时候没有动画效果,如果没有指定图片会什么都不显示(自定义时使用)。

    创建模型

    class MapFlag: NSObject, MKAnnotation {
        // 标题
        let title: String?
        // 副标题
        let subtitle: String?
        // 经纬度
        let coordinate: CLLocationCoordinate2D
        // 附加信息
        let urlString: String
    
        init(title: String?, subtitle: String?, coordinate: CLLocationCoordinate2D, urlString: String) {
            self.title = title
            self.subtitle = subtitle
            self.coordinate = coordinate
            self.urlString = urlString
        }
    }
    

    添加标注

    • 添加系统标注,点击能够显示标题和副标题。
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let flag = MapFlag(title: "标题", subtitle: "副标题", coordinate: CLLocationCoordinate2D(latitude: 31.2906511800, longitude: 118.3623587000), urlString: "https://www.baidu.com")
        mapView.addAnnotation(flag)
    }
    
    • 添加系统标注,点击以气泡形式显示标题、副标题及自定义内容,此时需要重写地图的代理方法,返回标注的样式。
    extension ViewController: MKMapViewDelegate {
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            guard let annotation = annotation as? MapFlag else {
                return nil
            }
            // 如果是用户的位置,使用默认样式
            if annotation == mapView.userLocation {
                return nil
            }
            // 标注的标识符
            let identifier = "marker"
            // 获取AnnotationView
            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView
            // 判空
            if annotationView == nil {
                annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
                // 显示气泡
                annotationView?.canShowCallout = true
                // 左边显示的辅助视图
                annotationView?.leftCalloutAccessoryView = UIImageView(image: UIImage(systemName: "heart"))
                // 右边显示的辅助视图
                let button = UIButton(type: .detailDisclosure, primaryAction: UIAction(handler: { _ in
                    print(annotation.urlString)
                }))
                annotationView?.rightCalloutAccessoryView = button
            }
    
            return annotationView
        }
    }
    
    • 如果希望标注的图标为自定义样式,只需要稍加更改代理方法并设置自己的标注图片即可。
    extension ViewController: MKMapViewDelegate {
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            guard let annotation = annotation as? MapFlag else {
                return nil
            }
            // 如果是用户的位置,使用默认样式
            if annotation == mapView.userLocation {
                return nil
            }
            // 标注的标识符
            let identifier = "custom"
            // 标注的自定义图片
            let annotationImage = ["pin.circle.fill", "car.circle.fill", "airplane.circle.fill", "cross.circle.fill"]
            // 获取AnnotationView
            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
            // 判空
            if annotationView == nil {
                annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
                // 图标,每次随机取一个
                annotationView?.image = UIImage(systemName: annotationImage.randomElement()!)
                // 显示气泡
                annotationView?.canShowCallout = true
                // 左边显示的辅助视图
                annotationView?.leftCalloutAccessoryView = UIImageView(image: UIImage(systemName: "heart"))
                // 右边显示的辅助视图
                let button = UIButton(type: .detailDisclosure, primaryAction: UIAction(handler: { _ in
                    print(annotation.urlString)
                }))
                annotationView?.rightCalloutAccessoryView = button
                // 弹出的位置偏移
                annotationView?.calloutOffset = CGPoint(x: -5.0, y: 5.0)
            }
    
            return annotationView
        }
    }
    
    // 点击地图插入一个标注,标注的标题和副标题显示的是标注的具体位置
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touchPoint = touches.first?.location(in: mapView)
        // 将坐标转换成为经纬度,然后赋值给标注
        let coordinate = mapView.convert(touchPoint!, toCoordinateFrom: mapView)
        let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
        let gecoder = CLGeocoder()
        // 反地理编码转换成具体的地址
        gecoder.reverseGeocodeLocation(location) { placeMarks, _ in
            let placeMark = placeMarks?.first
            if let placeMark = placeMark {
                let flag = MapFlag(title: placeMark.locality, subtitle: placeMark.subLocality, coordinate: coordinate, urlString: "https://www.baidu.com")
                self.mapView.addAnnotation(flag)
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:iOS14开发-定位与地图

        本文链接:https://www.haomeiwen.com/subject/wkikpltx.html