Yandex.Maps: Je suis allé au contrôleur de carte - j'ai immédiatement obtenu la position de l'utilisateur (d'accord, maintenant sérieusement)

Salutations encore!

Plus récemment, j'ai publié un article littéralement saturé d'amour pour Yandex.Maps. Le poème. Ode. Ici, en fait, elle habr.com/en/post/479102

Après m'être assuré que parmi les programmeurs il y a peu d'amateurs de poésie, j'ai néanmoins décidé d'éclairer la situation de manière plus «HABRovsky». Capturez un tas de code, de pensées et de captures d'écran. Allons-y.

image

Commençons à nouveau.

La tâche est triviale: lorsque vous entrez dans le contrôleur avec les cartes, vous devez immédiatement «faire un zoom arrière» sur le point de l'utilisateur (bonus: vous devrez également obtenir l'adresse sous une forme lisible avec tous les atibutes disponibles).

Nous avons coupé l'analyste: "Diviser pour régner".

Pour atteindre cet objectif, il est nécessaire de résoudre un certain nombre de problèmes techniques et commerciaux, à savoir:

0. Accédez au contrôleur avec un module potentiel pour travailler avec le géo-positionnement (MRSG), les rappels, etc.

1. IWG
1.1 Mettre en œuvre l'IWG
1.2 Lancer IWG
1.2.1 Obtenir les coordonnées de l'utilisateur
1.2.2 Regardez-le
2 *. Obtenez l'adresse de position dans un format lisible.

Passage au contrôleur (VIPER + Configurateur)

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

Le délégué n'a qu'une seule fonction de sorte que lorsque vous spécifiez le point souhaité, vous pouvez le renvoyer via un protocole au contrôleur qui appelle le contrôleur avec la carte:

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

Au délégué, nous envoyons la partie contrôle du code appelant. Tout semble clair ici.

C'est ainsi que nous allons sans prétention au contrôleur ...

image

Le réticule au centre est situé exactement au centre du contrôleur et au centre des cartes UIView. L'hypothèse selon laquelle le zoom à l'intérieur des cartes fonctionnera par défaut au centre de la fenêtre. Et il s'est avéré.

À gauche et juste en dessous du réticule - UILabel. Il est prévu d'y afficher une adresse lisible. Bouton droit avec UIActivityIndicator. Le fait est que jusqu'à ce que les coordonnées de l'utilisateur soient arrivées, il "tourne" et le bouton est assombri et désactivé. En cliquant sur le bouton avec les coordonnées reçues de l'utilisateur, nous lui rendons le réticule. Il s'agit d'une indication de la position, à partir de la position de l'utilisateur.

En bas se trouve le bouton "Select Point". En cliquant, la magie de la logique métier se produit:

 @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) } 

Hourra! Nous avons discuté de la phase préparatoire!

Nous passons au MRSG.

Vous trouverez ci-dessous un texte sous forme de tableau qui reflète personnellement mes évaluations (avec des éléments flous) des modules de carte intégrés les plus célèbres (à l'époque je ne connaissais pas www.openstreetmap.org , merci, daglob ).



«La tâche étant simple, j'utilise Yandex.Maps. Ils sont beaux, agiles ... "- pensai-je.

Si vous êtes intéressé par la façon de configurer cela, écrivez un mini-projet, mais si vous êtes trop paresseux - tech.yandex.ru/maps/mapkit/?from=mapsapi , prenez la trace de mon chemin. Commencer est facile.

Le fait est que la documentation est présentée sous cette forme:



Faites attention à la maigre description et à l'énorme liste d'objets à gauche. Maudit soit ta jambe.
"Le projet de test répondra probablement à mes questions." Et bien.

Voici cette bête. github.com/yandex/mapkit-ios-demo
Je n'y ai pas vu de solutions pour ma tâche triviale.
- D'accord, - je pense - j'ai assez d'expérience, si je ne suis pas développeur.



J'ai assemblé un projet de test et j'ai étudié la fonctionnalité de personnalisation du marqueur de l'utilisateur pendant longtemps.

Points clés:
Il y a un objet:

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

"Tout semble être logique", dites-vous. Mais non. Méthodes alternatives:

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

nous avons la possibilité d'arriver à point.lat et point.long de manière extrêmement difficile.
Par exemple, comme ceci:

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

Le temps d'attente pour les coordonnées de chargement varie avec cette approche de 2 à 50 secondes.

"Vous vous trompez, il doit y avoir un LocationManager concentré", me dis-je. Comme il s'est avéré plus tard - en effet, un tel "ami" est dans la documentation ... MAIS OERE EST UN EXEMPLE AVEC LUI?!?
Dans l'exemple de projet, il n'y a pas d'exemple d'application d'un tel gestionnaire:



- Eh bien, la documentation, il ne vous reste que vous et moi.
- Oui, il n'y a pas de problème, "innovateur", profitez:



Nous souscrivons l'UIViewController au protocole (j'espère qu'il n'y a pas besoin d'expliquer davantage quoi que ce soit ici, enfin, vraiment, les gars):

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

Et ...



1-15 secondes, CARL! 15! Parfois, cela fonctionne plus rapidement avec l'option précédente! Comment ça ??
Yandex, quelle blague? Tant de temps à consacrer à tout essayer et à obtenir un tel résultat - enfin, c'est généralement triste.

J'ai pensé, pensé ... Enfin, pas vraiment pareil. Donnez à une personne un contrôleur avec des cartes et mettez-le dans la stupeur lorsque vous passez dessus pendant plus de 4 secondes - c'est un suicide pour l'application. Personne n'attendra plus de 5 secondes en toute confiance que cela est confortable (si vous ne me croyez pas, écoutez les rapports de Vitaliy Fridman sur UI / UX).

Je pensais plus ... et l'émotion suivante était la suivante:


Qui veut du son - www.youtube.com/watch?v=pTZaNHZGsQo

La recette du succès était la suivante:
Prenez un kilo ... CLLocationManager et YMKLocationManager et ... faites-les fonctionner ensemble.



Ce «travail» conjoint ressemble à ceci:

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


... et le pilaf est prêt

Le résultat de la vitesse d'obtention du point de l'utilisateur: un peu plus de 0 seconde.

Le cas même où (à mon avis) lors de la résolution d'un problème trivial, l'opposition des parties natives et intégrées ressemblait à ceci:



Géocoder *
En bonus, je vais montrer l'implémentation du géocodeur.
Yandex Geocoder utilisé 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 } } 

Cette ressource aide beaucoup à convertir la réponse du serveur au modèle: app.quicktype.io

Le résultat du travail visuellement est le suivant:



Résumant ce qui précède:
collègues, l'article est écrit de telle sorte que lorsque vous résolvez un tel problème, vous ne passez pas autant de temps que moi et choisissez un chemin différent ou parcourez-le rapidement.

J'aimerais voir des critiques constructives et / ou des solutions alternatives correctes .

Si l'article s'est avéré utile pour vous, corrigez-moi avec une note Lois et, de préférence, consultez la version originale de cette histoire d'une nouvelle manière.

Tout succès créatif et humeur positive!

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


All Articles