jsdw_ios/QuickLocation/Section/Map/Navigation/NavigationVC.swift

323 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// NavigationVC.swift
// QuickLocation
//
// Created by 八条 on 2026/6/16.
//
import UIKit
import RxSwift
import RxCocoa
import CoreLocation
#if !targetEnvironment(simulator)
import AMapNaviKit
import MapKit
#endif
class NavigationVC: BaseViewController {
override var isNavigationBarHidden: Bool { true }
fileprivate var rootView: NavigationView!
private let member: CircleMember
private let currentUserCoord: CLLocationCoordinate2D?
private let groupName: String
private let groupIconIndex: String
#if !targetEnvironment(simulator)
private var routeOverlay: MAPolyline?
#endif
init(member: CircleMember, currentUserCoord: CLLocationCoordinate2D?, groupName: String = "", groupIcon: String = "") {
self.member = member
self.currentUserCoord = currentUserCoord
self.groupName = groupName
self.groupIconIndex = groupIcon
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
rootView = NavigationView(frame: UIScreen.main.bounds)
view = rootView
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// 页面被 pop/dismiss 时清理地图资源
if isMovingFromParent || isBeingDismissed {
#if !targetEnvironment(simulator)
rootView.cleanupMap()
#endif
}
}
override func viewDidLoad() {
super.viewDidLoad()
rootView.configure(member: member, groupName: groupName, groupIcon: groupIconIndex)
reactiveAction()
setupMap()
calculateDistance()
requestRoute()
}
// MARK: - Actions
private func reactiveAction() {
rootView.navigationBtn.rx.tap.subscribe(onNext: { _ in
self.touchGoMap()
}).disposed(by: disposeBag)
}
private func setupMap() {
#if !targetEnvironment(simulator)
rootView.mapView.delegate = self
// 添加起点标注(当前用户位置)
if let from = currentUserCoord, CLLocationCoordinate2DIsValid(from) {
let startAnn = MAPointAnnotation()
startAnn.coordinate = from
startAnn.title = "起点"
rootView.mapView.addAnnotation(startAnn)
}
#endif
}
private func requestRoute() {
#if !targetEnvironment(simulator)
guard let from = currentUserCoord,
CLLocationCoordinate2DIsValid(from),
CLLocationCoordinate2DIsValid(member.coordinate) else { return }
let naviManager = AMapNaviDriveManager.sharedInstance()
naviManager.delegate = self
guard let startPoint = AMapNaviPoint.location(withLatitude: CGFloat(from.latitude), longitude: CGFloat(from.longitude)),
let endPoint = AMapNaviPoint.location(withLatitude: CGFloat(member.coordinate.latitude), longitude: CGFloat(member.coordinate.longitude)) else { return }
naviManager.calculateDriveRoute(withStart: [startPoint], end: [endPoint], wayPoints: nil, drivingStrategy: .drivingStrategySingleDefault)
#endif
}
private func calculateDistance() {
guard CLLocationCoordinate2DIsValid(member.coordinate) else { return }
let memberLoc = CLLocation(latitude: member.coordinate.latitude, longitude: member.coordinate.longitude)
guard let userLoc = currentUserCoord.map({ CLLocation(latitude: $0.latitude, longitude: $0.longitude) }) else {
rootView.distanceLab.text = ""
return
}
let dist = memberLoc.distance(from: userLoc)
if dist < 1000 {
rootView.distanceLab.text = "距离 \(Int(dist)) 米"
} else {
rootView.distanceLab.text = "距离 \(String(format: "%.1f", dist / 1000)) 公里"
}
}
func touchGoMap() {
let coortitle = "目的地"
let latitute = member.coordinate.latitude
let longitute = member.coordinate.longitude
// "请选择导航应用程序"
let alert = UIAlertController(title: "请选择导航应用程序", message: nil, preferredStyle: .actionSheet)
if canOpenUrl("iosamap://") {//高德
alert.addAction(UIAlertAction(title: "高德地图", style: .default, handler: { _ in
self.amap(dlat: latitute, dlon: longitute, dname: coortitle, way: 0)
}))
}
if canOpenUrl("baidumap://") {//百度
alert.addAction(UIAlertAction(title: "百度地图", style: .default, handler: { _ in
self.baidumap(endAddress: coortitle, way: "driving", lat: latitute,lng: longitute)
}))
}
if canOpenUrl("qqmap://") {//腾讯
alert.addAction(UIAlertAction(title: "腾讯地图", style: .default, handler: { _ in
self.qqmap(endAddress: coortitle, way: "driving", lat: latitute,lng: longitute)
}))
}
alert.addAction(UIAlertAction(title: "Apple 地图", style: .default, handler: { _ in
self.appleMap(lat:latitute , lng:longitute, destination: coortitle)
}))
alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
#if !targetEnvironment(simulator)
// MARK: - AMapNaviDriveManagerDelegate
extension NavigationVC: AMapNaviDriveManagerDelegate {
func driveManager(onCalculateRouteSuccess driveManager: AMapNaviDriveManager) {
guard let route = driveManager.naviRoute else { return }
if let overlay = routeOverlay {
rootView.mapView.remove(overlay)
}
let coords = route.routeCoordinates.compactMap { point -> CLLocationCoordinate2D in
return CLLocationCoordinate2D(latitude: Double(point.latitude), longitude: Double(point.longitude))
}.filter { CLLocationCoordinate2DIsValid($0) }
guard coords.count > 1 else { return }
var mutableCoords = coords
if let polyline = MAPolyline(coordinates: &mutableCoords, count: UInt(coords.count)) {
rootView.mapView.add(polyline)
routeOverlay = polyline
rootView.mapView.setVisibleMapRect(polyline.boundingMapRect, edgePadding: UIEdgeInsets(top: 80, left: 50, bottom: 260, right: 50), animated: true)
}
let distM = Int(route.routeLength)
if distM < 1000 {
rootView.distanceLab.text = "距离 \(distM) 米"
} else {
rootView.distanceLab.text = "距离 \(String(format: "%.1f", Double(distM) / 1000)) 公里"
}
let endAnn = MAPointAnnotation()
endAnn.coordinate = member.coordinate
endAnn.title = member.name
rootView.mapView.addAnnotation(endAnn)
}
func driveManager(_ driveManager: AMapNaviDriveManager, onCalculateRouteFailure error: Error) {
print("Navi route failed: \(error.localizedDescription)")
}
}
// MARK: - MAMapViewDelegate
extension NavigationVC: MAMapViewDelegate {
func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
if annotation is MAUserLocation { return nil }
let identifier = "NavEnd"
var view = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if view == nil {
view = MAAnnotationView(annotation: annotation, reuseIdentifier: identifier)
} else {
view?.annotation = annotation
}
let isEnd = annotation.title == member.name
view?.image = UIImage(named: isEnd ? "Map/end" : "Map/current")
view?.centerOffset = CGPoint(x: 0, y: -22)
return view
}
func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! {
if let polyline = overlay as? MAPolyline {
let renderer = MAPolylineRenderer(polyline: polyline)
renderer?.strokeColor = UIColor(hexStr: "34B824")
renderer?.lineWidth = 6
return renderer
}
return nil
}
}
#endif
// MARK: - 地图导航
extension NavigationVC {
// 打开苹果地图
func appleMap(lat: Double, lng: Double, destination: String) {
let loc = CLLocationCoordinate2DMake(lat, lng)
let currentLocation = MKMapItem.forCurrentLocation()
let toLocation = MKMapItem(placemark:MKPlacemark(coordinate:loc,addressDictionary:nil))
toLocation.name = destination
let boo = MKMapItem.openMaps(with: [currentLocation,toLocation], launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving,MKLaunchOptionsShowsTrafficKey: NSNumber(value: true)])
print(boo)
}
// 打开高德地图
func amap(dlat: Double, dlon: Double, dname: String, way: Int) {
let appName = "极速定位"
let urlString = "iosamap://path?sourceApplication=\(appName)&dname=\(dname)&dlat=\(dlat)&dlon=\(dlon)&t=\(way)" as String
if self.openMap(urlString) == false {
print("您还没有安装高德地图")
let urlString = "itms-apps://itunes.apple.com/app/id452186370"
self.openURL(urlString: urlString)
}
}
// 打开腾讯地图
func qqmap(endAddress: String, way: String, lat: Double, lng: Double) {
let urlString = "qqmap://map/routeplan?type=\(way)&from=&fromcoord=CurrentLocation&to=\(endAddress)&tocoord=\(lat),\(lng)&referer=腾讯需要申请APPkey"
let str = urlString as String
if self.openMap(str) == false {
print("您还没有安装腾讯地图")
let urlString = "itms-apps://itunes.apple.com/app/id481623196"
self.openURL(urlString: urlString)
}
}
// 打开百度地图
func baidumap(endAddress: String, way: String, lat: Double, lng: Double) {
let coordinate = CLLocationCoordinate2DMake(lat, lng)
// 将高德的经纬度转为百度的经纬度
let baiduCoordinate = getBaiDuCoordinateByGaoDeCoordinate(coordinate: coordinate)
let destination = "\(baiduCoordinate.latitude),\(baiduCoordinate.longitude)"
let urlString = "baidumap://map/direction?" + "&destination=" + endAddress + "&mode=" + way + "&destination=" + destination
let str = urlString as String
if self.openMap(str) == false {
print("您还没有安装百度地图")
let urlString = "itms-apps://itunes.apple.com/app/id452186370" as String
self.openURL(urlString: urlString)
}
}
// 打开第三方地图
private func openMap(_ urlString: String) -> Bool {
let urlstr = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
guard let url = URL(string: urlstr) else {
return false
}
if UIApplication.shared.canOpenURL(url) == true {
self.openURL(urlString: urlString as String)
return true
} else {
return false
}
}
func openURL(urlString:String) {
let urlstr = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? ""
guard let url = URL(string:urlstr) else {
return
}
//根据iOS系统版本,分别处理
if #available(iOS 10, *) {
UIApplication.shared.open(url , options: [:],
completionHandler: { (success) in
})
} else {
UIApplication.shared.openURL(url )
}
}
// 高德经纬度转为百度地图经纬度
// 百度经纬度转为高德经纬度,减掉相应的值就可以了。
func getBaiDuCoordinateByGaoDeCoordinate(coordinate:CLLocationCoordinate2D) -> CLLocationCoordinate2D {
return CLLocationCoordinate2DMake(coordinate.latitude + 0.006, coordinate.longitude + 0.0065)
}
func canOpenUrl(_ urlString: String) -> Bool {
guard let url = URL(string: urlString) else { return false }
print("\(url) \(UIApplication.shared.canOpenURL(url))")
return UIApplication.shared.canOpenURL(url)
}
}