// // GroupScheduleVC.swift // QuickLocation // // Created by 八条 on 2026/6/29. // import UIKit import RxSwift import RxCocoa import ObjectMapper import SwiftyUserDefaults import AMapNaviKit import AMapSearchKit class GroupScheduleVC: BaseViewController { fileprivate var rootView: GroupScheduleView! override func loadView() { rootView = GroupScheduleView(frame: UIScreen.main.bounds) view = rootView } private let groupKey: String private var scheduleList: [ScheduleModel] = [] private var selectedIndex: Int? private let routeSearch = AMapSearchAPI() private var routeOverlays: [MAPolyline] = [] private var pointAnnotations: [MAPointAnnotation] = [] override func viewDidLoad() { super.viewDidLoad() rootView.tableView.dataSource = self rootView.tableView.delegate = self setupMap() requestGroupScheduleList() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) if isMovingFromParent || isBeingDismissed { rootView.cleanupMap() } } // MARK: - API private func requestGroupScheduleList() { DLToast.showLoading() ItineraryService.groupScheduleList(groupKey: groupKey).subscribe(onNext: { response in DLToast.dismiss() guard response.list.count > 0 else { DLToast.show(text: "暂无行程数据") { AppRouter.shared.popOrDismiss() } return } self.scheduleList = response.list self.rootView.tableView.reloadData() }).disposed(by: disposeBag) } // MARK: - Map private func setupMap() { rootView.mapView.delegate = self routeSearch?.delegate = self if let lat = Defaults[\.currentLatitude], let lon = Defaults[\.currentLongitude] { let coord = CLLocationCoordinate2D(latitude: lat, longitude: lon) if CLLocationCoordinate2DIsValid(coord) { rootView.mapView.setCenter(coord, animated: false) rootView.mapView.setZoomLevel(14, animated: false) } } } private func showScheduleOnMap(_ model: ScheduleModel) { // 清除旧标注和路线 for ann in pointAnnotations { rootView.mapView.removeAnnotation(ann) } for ol in routeOverlays { rootView.mapView.remove(ol) } pointAnnotations.removeAll() routeOverlays.removeAll() // 添加带序号的标注 let validPoints = model.points.filter { guard let lat = $0.latitude, let lon = $0.longitude else { return false } return abs(lat) > 0.0001 && abs(lon) > 0.0001 } for (i, p) in validPoints.enumerated() { let ann = MAPointAnnotation() ann.coordinate = CLLocationCoordinate2D(latitude: p.latitude!, longitude: p.longitude!) ann.title = "\(i + 1)" rootView.mapView.addAnnotation(ann) pointAnnotations.append(ann) } // 缩放至所有点位 if !pointAnnotations.isEmpty { rootView.mapView.showAnnotations(pointAnnotations, animated: true) } // 驾车路线 guard validPoints.count >= 2 else { return } let request = AMapDrivingRouteSearchRequest() request.origin = AMapGeoPoint.location(withLatitude: CGFloat(validPoints[0].latitude!), longitude: CGFloat(validPoints[0].longitude!)) request.destination = AMapGeoPoint.location(withLatitude: CGFloat(validPoints.last!.latitude!), longitude: CGFloat(validPoints.last!.longitude!)) if validPoints.count > 2 { var waypoints: [AMapGeoPoint] = [] for i in 1.. UIImage? { let size = CGSize(width: 20, height: 20) let rect = CGRect(origin: .zero, size: size) UIGraphicsBeginImageContextWithOptions(size, false, 0) guard let ctx = UIGraphicsGetCurrentContext() else { return nil } ctx.setLineWidth(1) ctx.setStrokeColor(UIColor.white.cgColor) ctx.setFillColor(UIColor(hexStr: "#16B3FF").cgColor) let path = UIBezierPath(ovalIn: rect) path.fill() path.stroke() let text = "\(num)" as NSString let attrs: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 11), .foregroundColor: UIColor.white] let strSize = text.size(withAttributes: attrs) text.draw(at: CGPoint(x: (size.width - strSize.width) / 2, y: (size.height - strSize.height) / 2)) let img = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return img } // MARK: - Init init(groupKey: String) { self.groupKey = groupKey super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - UITableViewDataSource & Delegate extension GroupScheduleVC: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { scheduleList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: GroupScheduleCell = tableView.dequeueReusableCell(for: indexPath) cell.configure(scheduleList[indexPath.row], isSelected: indexPath.row == selectedIndex) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectedIndex = indexPath.row tableView.reloadData() showScheduleOnMap(scheduleList[indexPath.row]) rootView.dismissPanel() } } // MARK: - MAMapViewDelegate extension GroupScheduleVC: MAMapViewDelegate { func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! { guard !(annotation is MAUserLocation), let pointAnn = annotation as? MAPointAnnotation else { return nil } if let num = Int(pointAnn.title ?? "") { let id = "SchedulePin" var view = mapView.dequeueReusableAnnotationView(withIdentifier: id) if view == nil { view = MAAnnotationView(annotation: annotation, reuseIdentifier: id) } else { view?.annotation = annotation } view?.image = Self.numberImage(num) view?.centerOffset = CGPoint(x: 0, y: -15) return view } return nil } func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! { if let polyline = overlay as? MAPolyline { let r = MAPolylineRenderer(polyline: polyline) r?.strokeColor = UIColor(hexStr: "#16B3FF") r?.lineWidth = 3 r?.lineDashType = kMALineDashTypeSquare return r } return nil } } // MARK: - AMapSearchDelegate extension GroupScheduleVC: AMapSearchDelegate { func onRouteSearchDone(_ request: AMapRouteSearchBaseRequest!, response: AMapRouteSearchResponse!) { guard let path = response.route?.paths?.first as? AMapPath else { return } var coords: [CLLocationCoordinate2D] = [] for step in path.steps { guard let polylineStr = step.polyline else { continue } for point in polylineStr.components(separatedBy: ";") { let latLon = point.components(separatedBy: ",") if latLon.count == 2, let lon = Double(latLon[0]), let lat = Double(latLon[1]) { coords.append(CLLocationCoordinate2D(latitude: lat, longitude: lon)) } } } guard coords.count > 1 else { return } var mutableCoords = coords if let polyline = MAPolyline(coordinates: &mutableCoords, count: UInt(coords.count)) { rootView.mapView.add(polyline) routeOverlays.append(polyline) } } func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) { print("Route error: \(error.localizedDescription)") } }