【发布时间】: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 course、this Ray Wenderlich tutorial 和 this 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?
mapView 委托设置为 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