// // 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 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) } }