【问题标题】:Polyline overlay is not showing up on MapView折线覆盖未显示在 MapView 上
【发布时间】:2021-04-30 04:13:09
【问题描述】:

我目前正在使用 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(显示 Starbucks 的注释和用户当前位置,但没有覆盖)。

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

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

以下是相关的代码行:

路由和mapView等属性声明

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

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

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

地图视图函数

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

TableView didSelectRowAt 方法

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, animated: true, 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, left: view.safeAreaLayoutGuide.leftAnchor, 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, 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 = 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, 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)
            
        }
    }
}

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

2021-01-26 19:36:40.853479+0700 Uber[17163:1579722] [Default] InfoLog PolylineOverlayFillShader: WARNING: 0:42: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:48: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:54: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:66: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:68: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:72: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:74: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:78: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)
WARNING: 0:80: Overflow in implicit constant conversion, minimum range for lowp float is (-2,2)

提前致谢!

【问题讨论】:

  • 代码是在你的'rendererFor'函数中执行的吗?看起来您正在“generatePolyline”中添加覆盖路线,然后根据 self.route 再次创建它
  • @Russell 如果我从“rendererFor”函数中删除if let route = self.route { let polyline = route.polyline },并用let lineRenderer = MKPolylineRenderer(overlay: overlay) 替换let lineRenderer = MKPolylineRenderer(overlay: polyline),同样的事情也会发生(没有覆盖)。 'rendererFor' 函数不只是通过'self.mapView.addOverlay(polyline)' 行自动调用吗?抱歉,我是一个相对初学者,所以我不能 100% 确定幕后发生了什么。

标签: ios swift mapkit overlay polyline


【解决方案1】:

我已经创建了一个简化版本 - 使用硬编码的目的地,但像你一样创建路线,这非常好。

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
}

如果您看到调试打印 cmets,则应该没问题。让我知道你过得怎么样...

【讨论】:

  • 感谢您的回复 - 似乎它不起作用的原因是我在柬埔寨,Apple Maps 似乎还无法显示柬埔寨境内的方向。在深入挖掘之后,我打印了来自directionRequest.calculate { (response, error) in 的错误消息,并显示“此位置不提供路线”。然后我检查了地图应用程序,它做了同样的事情......我想如果我想画一条线,我将不得不求助于谷歌地图。
  • 真可惜。我没想过只是找不到方向!
  • 我在首都,所以我也没想到!无论如何感谢您的帮助。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多