Yandex.Maps: Ich bin zum Karten-Controller gegangen - ich habe sofort die Position des Benutzers erhalten (okay, jetzt im Ernst)

Grüße nochmal!

Kürzlich habe ich einen Artikel veröffentlicht, der von der Liebe zu Yandex.Maps sprichwörtlich erfüllt ist. Das Gedicht. Ode. Hier tatsächlich habr.com/en/post/479102

Nachdem ich sichergestellt hatte, dass es unter den Programmierern nur wenige Liebhaber von Gedichten gibt, entschied ich mich dennoch, die Situation auf eine eher „HABRovsky-artige“ Weise zu beleuchten. Fange eine Menge Code, Gedanken und Screenshots ein. Lass uns gehen.

Bild

Fangen wir von vorne an.

Die Aufgabe ist trivial: Wenn Sie den Controller mit den Karten betreten, müssen Sie sofort zum Punkt des Benutzers „herausquetschen“ (Bonus: Sie müssten auch die Adresse in einer lesbaren Form zusammen mit allen verfügbaren Attributen erhalten).

Wir schneiden den Analytiker: "Teilen und erobern."

Um dieses Ziel zu erreichen, müssen eine Reihe von technischen und geschäftlichen Problemen gelöst werden:

0. Gehen Sie zur Steuerung mit einem potenziellen Modul für die Arbeit mit Geopositionierung (MRSG), Rückrufen usw.

1. IWG
1.1 Implementieren Sie die IWG
1.2 Starten Sie IWG
1.2.1 Benutzerkoordinaten abrufen
1.2.2 Schau ihn dir an
2 *. Holen Sie sich die Positionsadresse in einem lesbaren Format.

Umstieg auf die Steuerung (VIPER + Configurator)

extension AddPresenter: AddPresentationLogic { // ... func openMap(_ delegate: YandexMapSetPointViewControllerProtocol?) { router.routeTo(target: .addPlace(delegate)) } // ... } 

Der Delegat hat nur eine Funktion. Wenn Sie den gewünschten Punkt angeben, können Sie ihn per Protokoll an den Controller zurückgeben, der den Controller mit der Karte aufruft:

 protocol YandexMapSetPointViewControllerProtocol { func didSelectPoint(_ place: Place) } 

An den Delegierten senden wir den Steuerteil des aufrufenden Codes. Hier scheint alles klar zu sein.

So unprätentiös gehen wir zum Controller ...

Bild

Das Fadenkreuz in der Mitte befindet sich genau in der Mitte des Controllers und in der Mitte der UIView-Karten. Die Annahme, dass das Zoomen in Karten standardmäßig in der Mitte des Fensters funktioniert. Und so stellte sich heraus.

Links und direkt unter dem Fadenkreuz - UILabel. Es ist geplant, dort eine lesbare Adresse anzuzeigen. Rechte Taste mit UIActivityIndicator. Der Punkt ist, dass der Benutzer bis zum Eintreffen seiner Koordinaten "rotiert" und die Schaltfläche abgedunkelt und deaktiviert ist. Durch Anklicken des Buttons mit den empfangenen Koordinaten des Benutzers senden wir ihm das Fadenkreuz zurück. Dies ist eine Anzeige der Position, beginnend mit der Position des Benutzers.

Unten befindet sich die Schaltfläche "Punkt auswählen". Durch Klicken entsteht die Magie der Geschäftslogik:

 @IBAction func selectButtonWasPressed(_ sender: Any) { let place = Place() place.name = " " place.point.latitude = "\(String(describing: selectedPoint!.latitude))" place.point.longitude = "\(String(describing: selectedPoint!.longitude))" place.addressText = selectedPointGeocoderedAddress delegate?.didSelectPoint(place) navigationController?.popViewController(animated: true) } 

Hurra! Wir haben die Vorbereitungsphase besprochen!

Wir gehen zur MRSG.

Im Folgenden finden Sie einen tabellenformatierten Text, der meine Einschätzungen (mit unscharfen Elementen) der bekanntesten (zu dieser Zeit wusste ich nichts über www.openstreetmap.org , danke, daglob ) integrierten Kartenmodule widerspiegelt .



„Da die Aufgabe einfach ist, verwende ich Yandex.Maps. Sie sind schön, flink ... "- dachte ich.

Wenn Sie daran interessiert sind, dies zu konfigurieren, schreiben Sie ein Mini-Projekt, aber wenn Sie zu faul sind - tech.yandex.ru/maps/mapkit/?from=mapsapi , folgen Sie meinem Weg. Der Einstieg ist einfach.

Fakt ist, dass die Dokumentation in dieser Form vorliegt:



Beachten Sie die magere Beschreibung und die riesige Liste der Objekte auf der linken Seite. Verdammt dein Bein.
"Wahrscheinlich wird das Testprojekt meine Fragen beantworten." Na ja

Hier ist dieses Biest. github.com/yandex/mapkit-ios-demo
Ich habe dort keine Lösungen für meine triviale Aufgabe gesehen.
- Okay, - ich denke - ich habe genug Erfahrung, wenn ich kein Entwickler bin.



Ich habe ein Testprojekt zusammengestellt und mich lange mit der Möglichkeit befasst, die Markierung des Benutzers anzupassen.

Wichtige Punkte:
Es gibt ein Objekt:

 @IBOutlet weak var mapView: YMKMapView! //  YMKUserLocationObjectListener - ,   

"Alles scheint logisch zu sein", sagen Sie. Aber nein Alternative Methoden:

  func onObjectAdded(with view: YMKUserLocationView) {} func onObjectRemoved(with view: YMKUserLocationView) {} func onObjectUpdated(with view: YMKUserLocationView, event: YMKObjectEvent) {} 

Wir haben die Möglichkeit, auf äußerst schwierige Weise auf point.lat und point.long zuzugreifen.
Zum Beispiel so:

 userLocation = YMKPoint(latitude: view.pin.geometry.latitude, longitude: view.pin.geometry.longitude) 

Die Wartezeit für das Laden von Koordinaten variiert bei diesem Ansatz zwischen 2 und 50 Sekunden.

"Sie irren sich, es muss einen fokussierten LocationManager geben", sagte ich mir. Wie sich später herausstellte - tatsächlich ist ein solcher "Freund" in der Dokumentation ... ABER WO IST EIN BEISPIEL MIT IHM?!?
Im Beispielprojekt gibt es kein Beispiel für die Anwendung eines solchen Managers:



- Nun, Dokumentation, nur du und ich sind übrig.
- Ja, es gibt keine Probleme, "Innovator", genießen Sie:



Wir abonnieren den UIViewController für das Protokoll (ich hoffe, es gibt hier keine weiteren Erklärungen):

 // MARK: - // Params var userLocation: YMKPoint? { didSet { guard userLocation != nil && userLocation?.latitude != 0 && userLocation?.longitude != 0 else { return } if isItFirstSelection { isItFirstSelection = false selectedPoint = userLocation mapView.mapWindow.map.move( with: YMKCameraPosition.init(target: userLocation!, zoom: 16, azimuth: 0, tilt: 0), animationType: YMKAnimation(type: YMKAnimationType.smooth, duration: 1), cameraCallback: nil) } activityIndicator.stopAnimating() } } // MARK: - // Some like didLoad setupLocationManager() // MARK: - // Setup private func setupLocationManager() { locationManager = YMKMapKit.sharedInstance()!.createLocationManager() locationManager.subscribeForLocationUpdates(withDesiredAccuracy: 0, minTime: 10, minDistance: 0, allowUseInBackground: true, filteringMode: .on, locationListener: self) } // MARK: - // MARK: YMKLocationDelegate extension YandexMapSetPointViewController: YMKLocationDelegate { func onLocationUpdated(with location: YMKLocation) { userLocation = YMKPoint(latitude: location.position.latitude, longitude: location.position.longitude) } func onLocationStatusUpdated(with status: YMKLocationStatus) {} } 

Und ...



1-15 Sekunden, CARL! 15! Manchmal klappt es mit der vorherigen Option schneller! Wie so ??
Yandex, was für ein Witz? So viel Zeit, um alles auszuprobieren und ein solches Ergebnis zu erzielen - nun, es ist im Allgemeinen traurig.

Ich dachte, dachte ... Nun, nicht wirklich dasselbe. Geben Sie einer Person einen Controller mit Karten und versetzen Sie sie in einen Stupor, wenn Sie länger als 4 Sekunden darauf umschalten - dies ist Selbstmord für die Anwendung. Niemand wird mehr als 5 Sekunden warten, mit der Gewissheit, dass dies bequem ist (wenn Sie mir nicht glauben, hören Sie sich Vitaliy Fridmans Berichte über UI / UX an).

Ich dachte mehr ... und das folgende Gefühl war das:


Wer will mit Sound - www.youtube.com/watch?v=pTZaNHZGsQo

Das Erfolgsrezept war:
Nehmen Sie ein Kilo ... CLLocationManager und YMKLocationManager und ... lassen Sie sie zusammenarbeiten.



Diese gemeinsame "Arbeit" sieht ungefähr so ​​aus:

 // Params private var locationManager: YMKLocationManager! private var nativeLocationManager = CLLocationManager() // MARK: - // Some like didLoad setupNativeLocationManager() // MARK: - // Setup private func setupNativeLocationManager() { if CLLocationManager.locationServicesEnabled() { nativeLocationManager.delegate = self nativeLocationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters nativeLocationManager.startUpdatingLocation() } } // MARK: - // CLLocationManagerDelegate extension YandexMapSetPointViewController: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { userLocation = YMKPoint(latitude: locations.last!.coordinate.latitude, longitude: locations.last!.coordinate.longitude) } } 


... und der Pilaw ist fertig

Das Ergebnis der Geschwindigkeit, mit der der Benutzer den Punkt erhält: etwas mehr als 0 Sekunden.

Gerade als (meiner Meinung nach) bei der Lösung eines trivialen Problems der Gegensatz von nativen und eingebetteten Teilen so aussah:



Geocoder *
Als Bonus zeige ich die Implementierung des Geocoders.
Gebraucht Yandex Geocoder tech.yandex.ru/maps/geocoder

 // Params private let geocoderManager = GeocoderManager.shared //      .     import Foundation import Alamofire import Alamofire_SwiftyJSON import SwiftyJSON import PromiseKit import UIKit // MARK: - // MARK: GeocoderManager class GeocoderManager { static let shared = GeocoderManager() private init() {} func getAddressBy(latitude: String, longitude: String, completion: @escaping (Bool, String?, Error?)->()) { GeocoderAPI().request(latitude: latitude, longitude: longitude).done { (response) in completion(true, response.getAddress(), nil) print("success") }.catch { (error) in completion(false, nil, error) } } } // import Foundation import Alamofire import PromiseKit // MARK: - // MARK: Request enum GeocoderRequest { case addressRequest(String, String) } // MARK: - // MARK: GeocoderRequest extension GeocoderRequest: DefaultRequest { var path: String { switch self { case .addressRequest: return "1.x/" } } var method: HTTPMethod { switch self { case .addressRequest: return .get } } var headers: HTTPHeaders { return [:] } var parameters: [String: Any]? { switch self { case .addressRequest(let latitude, let longitude): return [ "apikey" : Consts.APIKeys.yandexGeocoderKey, "format" : "json", "results" : 1, "spn" : "3.552069,2.400552", "geocode" : "\(longitude),\(latitude)" ] } } func asURLRequest() throws -> URLRequest { let url = try GlobalConsts.Links.geocoderBaseURL.asURL()// not good, need new idea for this var urlRequest = URLRequest(url: url.appendingPathComponent(path)) urlRequest.httpMethod = method.rawValue urlRequest.allHTTPHeaderFields = headers switch method { case .get: urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters) case .post: urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters) case .put: urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters) case .patch: urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters) case .delete: urlRequest = try JSONEncoding.default.encode(urlRequest, with: parameters) default: break } return urlRequest } } 

Diese Ressource hilft sehr, die Antwort vom Server auf das Modell zu konvertieren: app.quicktype.io

Das Ergebnis der Arbeit ist visuell wie folgt:



Zusammenfassend gesagt:
Kollegen, der Artikel ist so geschrieben, dass Sie bei der Lösung eines solchen Problems nicht so viel Zeit aufwenden wie ich und entweder einen anderen Weg wählen oder ihn schnell beschreiten.

Ich würde gerne konstruktive Kritik und / oder alternative richtige Lösungen sehen.

Wenn sich der Artikel als nützlich für Sie herausgestellt hat, korrigieren Sie mich mit einer Lois-Bewertung und lesen Sie die Originalversion dieser Geschichte auf eine neue Art und Weise.

Alles kreative Erfolge und gute Laune!

Source: https://habr.com/ru/post/de479432/


All Articles