折线叠加未显示在 MapView 上

问题描述

我目前正在使用 Swift 和 Firebase 构建 Uber 克隆,跟随 Stephan Dowless 的 course on Udemy 以了解有关 MapKit 的更多信息,到目前为止进展顺利,但我正在努力向地图,显示从用户当前位置到注释的路线(通过点击 tableView 中显示的搜索结果之一添加到 mapView)。

我在 SO 上寻找了其他类似的问题,但没有找到任何可以回答我的问题的问题。我还尝试克隆其他使用此功能的项目(the completed Uber clone from the Udemy coursethis Ray Wenderlich tutorialthis article on polylines using SwiftUI)来检查是否是我的代码有问题,但它们都存在相同的问题,即注释出现在屏幕上,但根本不出现叠加层。

在我的应用中,点击 this screen 中 tableView 上的“Starbucks”会产生 this screen(显示星巴克和用户当前位置的注释,但没有叠加)。

同样,运行前面提到的 SwiftUI MapKit tutorial app from Medium 会显示 this(两个注释但没有叠加)。

这让我相信这是我这边出了问题。我也试过在我的手机 (iPhone 7) 上运行这些应用,但遇到了同样的问题。

以下是相关的代码行:

属性声明,包括 route 和 mapView

quick brown fox

ma​​pView 委托设置为 self(确认用户登录时调用的函数)

// MARK:- Properties
    
    private let mapView = MKMapView()
    private var searchResults = [MKPlacemark]()
    private var route: MKRoute?

MapView 函数

private func configureMapView() {
        view.addSubview(mapView)
        mapView.frame = view.frame
        
        mapView.delegate = self
        
        mapView.showsUserLocation = true
        mapView.userTrackingMode = .follow
    }

TableView didSelectRowAt 方法

// MARK:- MapView Functions

extension HomeViewController: MKMapViewDelegate {
    
    private func generatePolyline(toDestination destination: MKMapItem) {
        let request = MKDirections.Request()
        request.source = MKMapItem.forCurrentLocation()
        request.destination = destination
        request.transportType = .automobile
        
        let directionRequest = MKDirections(request: request)
        directionRequest.calculate { (response,error) in
            guard let response = response else { return }
            self.route = response.routes[0]
            guard let polyline = self.route?.polyline else { return }
            self.mapView.addOverlay(polyline)
            
        }
    }
    
    private func searchBy(naturalLanguageQuery: String,completion: @escaping([MKPlacemark]) -> Void) {
        var results = [MKPlacemark]()
        
        let request = MKLocalSearch.Request()
        request.region = mapView.region
        request.naturalLanguageQuery = naturalLanguageQuery
        
        let search = MKLocalSearch(request: request)
        search.start { (response,error) in
            guard let response = response else { return }
            
            response.mapItems.forEach { (item) in
                results.append(item.placemark)
            }
            completion(results)
        }
        
    }
    
    // Change driver annotation appearance to Uber arrow
    public func mapView(_ mapView: MKMapView,viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if let annotation = annotation as? DriverAnnotation {
            let view = MKAnnotationView(annotation: annotation,reuseIdentifier: DriverAnnotation.identifier)
            view.image = #imageLiteral(resourceName: "chevron-sign-to-right")
            return view
        }
        return nil
    }
    
    func mapView(_ mapView: MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        if let route = self.route {
            let polyline = route.polyline
            let lineRenderer = MKPolylineRenderer(overlay: polyline)
            lineRenderer.strokeColor = .mainBlueTint
            lineRenderer.lineWidth = 4
            return lineRenderer
        }
        return MKOverlayRenderer()
    }
}

最后,这是整个视图控制器,以防万一我错过了上面的任何内容。如果回答问题需要更多信息,请告诉我,我会提供。

整个视图控制器

func tableView(_ tableView: UITableView,didSelectRowAt indexPath: IndexPath) {
        let placemark = self.searchResults[indexPath.row]
        
        configureActionButtonState(config: .dismissActionView)
        
        let destination = MKMapItem(placemark: placemark)
        self.generatePolyline(toDestination: destination)
        
        self.dismissInputView { _ in
            let annotation = MKPointAnnotation()
            annotation.coordinate = placemark.coordinate
            self.mapView.addAnnotation(annotation)
            self.mapView.selectAnnotation(annotation,animated: true)
            
        }
    }

这也是调试窗口中的输出,不确定它是否相关:

import UIKit
import Firebase
import MapKit

private enum ActionButtonConfiguration {
    case showMenu
    case dismissActionView
    
    init() {
        self = .showMenu
    }
}

class HomeViewController: UIViewController {
    
    // MARK:- Properties
    
    private let mapView = MKMapView()
    private let locationManager = LocationHandler.shared.locationManager
    private let inputActivationView = LocationInputActivationView()
    private let locationInputView = LocationInputView()
    private var searchResults = [MKPlacemark]()
    private var actionButtonConfig = ActionButtonConfiguration()
    private var route: MKRoute?
    
    private let tableView = UITableView()
    
    private var user: User? {
        didSet {
            locationInputView.user = user
        }
    }
    
    private let actionButton: UIButton = {
       let button = UIButton()
        button.setImage(#imageLiteral(resourceName: "baseline_menu_black_36dp").withRenderingMode(.alwaysOriginal),for: .normal)
        button.addTarget(self,action: #selector(didTapActionButton),for: .touchUpInside)
        return button
    }()
    
    
    // MARK:- Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        checkIfUserLoggedIn()
        locationManagerDidChangeAuthorization(locationManager!)
        
    }
    
    // MARK:- Selectors
    
    @objc private func didTapActionButton() {
        switch actionButtonConfig {
        case .showMenu:
            print("Show menu")
        case .dismissActionView:
            removeAnnotationsAndOverlays()
            
            UIView.animate(withDuration: 0.3) {
                self.configureActionButtonState(config: .showMenu)
                self.inputActivationView.alpha = 1
            }
        }
    }
    
    // MARK:- API
    
    private func fetchUserData() {
        guard let currentUserId = Auth.auth().currentUser?.uid else { return }
        Service.shared.fetchUserData(uid: currentUserId) { user in
            self.user = user
        }
        
    }
    
    // N.B. Service.shared.fetchDrivers automatically gets called every time the location of the driver changes since it is observing the database via geofire (see definition of this within Service.swift)
    private func fetchDrivers() {
        guard let location = locationManager?.location else { return }
        Service.shared.fetchDrivers(location: location) { (driver) in
            guard let coordinate = driver.location?.coordinate else { return }
            let annotation = DriverAnnotation(uid: driver.uid,coordinate: coordinate)
            
            var driverIsVisible: Bool {
                return self.mapView.annotations.contains { annotation -> Bool in
                    guard let driverAnnotation = annotation as? DriverAnnotation else { return false }
                    if driverAnnotation.uid == driver.uid {
                        // Driver is already visible - update driver location whenever this function is called
                        driverAnnotation.updateAnnotationPosition(withCoordinate: coordinate)
                        return true
                    }
                    
                    // Driver is not visible
                    return false
                }
                
            }
            
            // If driver is not visible then add to map
            if !driverIsVisible {
                self.mapView.addAnnotation(annotation)
            }
            
            
        }
    }
    
    private func checkIfUserLoggedIn() {
        if Auth.auth().currentUser == nil {
            // User is not logged in
            print("DEBUG: User is not logged in")
            DispatchQueue.main.async {
                let nav = UINavigationController(rootViewController: LoginViewController())
                nav.isModalInPresentation = true
                nav.modalPresentationStyle = .fullScreen
                self.present(nav,animated: true,completion: nil)
            }
        } else {
            // User is logged in
            configure()
        }
        
    }
    
    private func logOut() {
        do {
            try Auth.auth().signOut()
            DispatchQueue.main.async {
                let nav = UINavigationController(rootViewController: LoginViewController())
                nav.isModalInPresentation = true
                nav.modalPresentationStyle = .fullScreen
                self.present(nav,completion: nil)
            }
        } catch {
            print("DEBUG: Error signing user out: \(error)")
        }
    }
    
    // MARK:- Public Helper Functions
    
    public func configure() {
        configureUI()
        fetchUserData()
        fetchDrivers()
    }
    
    public func configureUI() {
        configureMapView()
        configureActionButton()
        configureInputActivationView()
        configureTableView()
    }
    
    // MARK:- Private Helper Functions
    
    private func configureActionButton() {
        view.addSubview(actionButton)
        actionButton.anchor(top: view.safeAreaLayoutGuide.topAnchor,left: view.safeAreaLayoutGuide.leftAnchor,paddingTop: 16,paddingLeft: 16,width: 30,height: 30)
    }
    
    private func configureActionButtonState(config: ActionButtonConfiguration) {
        switch config {
        case .showMenu:
            self.actionButton.setImage(#imageLiteral(resourceName: "baseline_menu_black_36dp").withRenderingMode(.alwaysOriginal),for: .normal)
            self.actionButtonConfig = .showMenu
        case .dismissActionView:
            self.actionButton.setImage(#imageLiteral(resourceName: "baseline_arrow_back_black_36dp-1").withRenderingMode(.alwaysOriginal),for: .normal)
            self.actionButtonConfig = .dismissActionView
        }
    }
    
    private func configureMapView() {
        view.addSubview(mapView)
        mapView.frame = view.frame
        
        mapView.delegate = self
        
        mapView.showsUserLocation = true
        mapView.userTrackingMode = .follow
    }
    
    private func configureInputActivationView() {
        inputActivationView.delegate = self
        
        view.addSubview(inputActivationView)
        inputActivationView.centerX(inView: view)
        inputActivationView.anchor(top: actionButton.bottomAnchor,right: view.safeAreaLayoutGuide.rightAnchor,paddingTop: 18,paddingLeft: 20,paddingRight: 20,height: 40)
        
        // Animate inputActivationView (fade in)
        inputActivationView.alpha = 0
        UIView.animate(withDuration: 2) {
            self.inputActivationView.alpha = 1
        }
    }
    
    private func configureLocationInputView() {
        locationInputView.delegate = self
        
        view.addSubview(locationInputView)
        locationInputView.anchor(top: view.topAnchor,left: view.leftAnchor,right: view.rightAnchor,height: 200)
        locationInputView.alpha = 0
        
        UIView.animate(withDuration: 0.5) {
            self.locationInputView.alpha = 1
        } completion: { _ in
            print("DEBUG: Present table view")
            UIView.animate(withDuration: 0.3) {
                self.tableView.frame.origin.y = self.locationInputView.frame.height
            }
        }
    }
    
    private func configureTableView() {
        view.addSubview(tableView)
        
        tableView.delegate = self
        tableView.dataSource = self
        
        tableView.register(LocationTableViewCell.self,forCellReuseIdentifier: LocationTableViewCell.identifier)
        
        tableView.rowHeight = 60
        tableView.tableFooterView = UIView()
        
        let height = view.frame.height - locationInputView.frame.height
        tableView.frame = CGRect(x: 0,y: view.frame.height,width: view.frame.width,height: height)
    }
    
    private func dismissInputView(completion: ((Bool) -> Void)? = nil) {
        UIView.animate(withDuration: 0.3,animations: {
            self.locationInputView.alpha = 0
            self.tableView.frame.origin.y = self.view.frame.height
            self.locationInputView.removeFromSuperview()
        },completion: completion)
    }
    
    private func removeAnnotationsAndOverlays() {
        mapView.annotations.forEach { annotation in
            if let anno = annotation as? MKPointAnnotation {
                mapView.removeAnnotation(anno)
            }
        }
        if mapView.overlays.count > 0 {
            mapView.removeOverlay(mapView.overlays[0])
        }
    }
    
}

// MARK:- MapView Functions

extension HomeViewController: MKMapViewDelegate {
    
    private func generatePolyline(toDestination destination: MKMapItem) {
        let request = MKDirections.Request()
        request.source = MKMapItem.forCurrentLocation()
        request.destination = destination
        request.transportType = .automobile
        
        let directionRequest = MKDirections(request: request)
        directionRequest.calculate { (response,rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        if let route = self.route {
            let polyline = route.polyline
            let lineRenderer = MKPolylineRenderer(overlay: polyline)
            lineRenderer.strokeColor = .mainBlueTint
            lineRenderer.lineWidth = 3
            return lineRenderer
        }
        return MKOverlayRenderer()
    }
}

// MARK:- Location Manager Services

extension HomeViewController {
    
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        
        switch manager.authorizationStatus {
        case .notDetermined:
            print("DEBUG: Not determined")
            locationManager?.requestWhenInUseAuthorization()
        case .restricted:
            break
        case .denied:
            break
        case .authorizedAlways:
            print("DEBUG: Auth always")
            locationManager?.startUpdatingLocation()
            locationManager?.desiredAccuracy = kCLLocationAccuracyBest
        case .authorizedWhenInUse:
            print("DEBUG: Auth when in use")
            locationManager?.requestAlwaysAuthorization()
        @unknown default:
            break
        }
    }
}

// MARK:- Input Activation View Delegate Methods

extension HomeViewController: LocationInputActivationViewDelegate {
    func presentLocationInputView() {
        configureLocationInputView()
        self.inputActivationView.alpha = 0
    }
    
}

// MARK:- Input View Delegate Methods

extension HomeViewController: LocationInputViewDelegate {
    func executeSearch(query: String) {
        searchBy(naturalLanguageQuery: query) { (results) in
            print("DEBUG: Placemarks are \(results)")
            self.searchResults = results
            self.tableView.reloadData()
        }
        
    }
    
    func dismissLocationInputView() {
        dismissInputView()
        UIView.animate(withDuration: 0.5) {
            self.inputActivationView.alpha = 1
        }
    }
}

// MARK:- TableView Delegate and Datasource Methods

extension HomeViewController: UITableViewDelegate,UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int {
        2
    }
    
    func tableView(_ tableView: UITableView,titleForHeaderInSection section: Int) -> String? {
        return "test"
    }
    
    func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        return section == 0 ? 2 : searchResults.count
    }
    
    func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: LocationTableViewCell.identifier,for: indexPath) as! LocationTableViewCell
        if indexPath.section == 1 {
            cell.placemark = searchResults[indexPath.row]
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView,animated: true)
            
        }
    }
}

提前致谢!

解决方法

我已经创建了一个简化版本 - 使用硬编码的目的地,但按照您的方式创建路线,这非常有效。

private func generatePolyline(toDestination destination: MKMapItem) {
    let request = MKDirections.Request()
    request.source = MKMapItem.forCurrentLocation()
    request.destination = destination
    request.transportType = .automobile
    
    let directionRequest = MKDirections(request: request)
    directionRequest.calculate { (response,error) in
        guard let response = response else { return }
        self.route = response.routes[0]
        guard let polyline = self.route?.polyline else { return }
        self.mapView.addOverlay(polyline)
        print("update map?")
    }
}

我在这里添加的唯一内容是最后的调试打印语句 - 总是很高兴知道某些事情应该发生,但知道它确实发生了总是很高兴!

我从一个简单的测试按钮调用了这个函数

@IBAction func cmdButton(_ sender: Any) {
    // for testing - Edinburgh Castle
    generatePolyline(toDestination: MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2D(latitude:  55.9483,longitude: -3.1981),addressDictionary: nil)))
}

渲染器非常简单,包括上面的测试打印

func mapView(_ mapView: MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let pr = MKPolylineRenderer(overlay: overlay)
        pr.strokeColor = UIColor.red.withAlphaComponent(0.5)
        pr.lineWidth = 7
        print("rendererFor overlay")
        return pr
}

如果您看到调试打印注释,您应该没问题。让我知道你的进展如何...

,

深入挖掘后,原因是我在柬埔寨金边。 Apple Maps 无法在柬埔寨提供方向。如果我想从一个点到另一个点画一条线,我猜我将不得不求助于谷歌地图。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...