323 lines
12 KiB
Swift
323 lines
12 KiB
Swift
//
|
||
// 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)
|
||
}
|
||
}
|