Yandex.Maps: Fui ao controlador da placa - imediatamente obtive a posição do usuário (ok, agora sério)

Saudações de novo!

Mais recentemente, publiquei um artigo literalmente saturado de amor pelo Yandex.Maps. O poema. Ode. Aqui, de fato, ela habr.com/en/post/479102

Tendo me assegurado de que há poucos amantes da poesia entre os programadores, decidi esclarecer a situação de uma maneira mais "HABRovsky". Pegue um monte de código, pensamentos e capturas de tela. Vamos lá

imagem

Vamos começar de novo.

A tarefa é trivial: quando você entra no controlador com os cartões, precisa "espremer" imediatamente para o ponto do usuário (bônus: você também precisa obter o endereço de forma legível, juntamente com todos os atributos disponíveis).

Cortamos o analista: "Divida e conquiste".

Para atingir esse objetivo, é necessário resolver vários problemas técnicos e de negócios, a saber:

0. Vá para o controlador com um módulo em potencial para trabalhar com posicionamento geográfico (MRSG), retornos de chamada etc.

1. IWG
1.1 Implementar o IWG
1.2 Iniciar o IWG
1.2.1 Obter coordenadas do usuário
1.2.2 Dê uma olhada nele
2 *. Obtenha o endereço da posição em um formato legível.

Mudando para o controlador (VIPER + Configurator)

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

O delegado possui apenas uma função para que, quando você especificar o ponto desejado, possa devolvê-lo via protocolo ao controlador que chama o controlador com o cartão:

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

Para o delegado, enviamos a parte de controle do código de chamada. Tudo parece estar claro aqui.

É assim que despretensioso vamos ao controlador ...

imagem

A mira no centro está localizada exatamente no centro do controlador e no centro dos mapas do UIView. A suposição de que o zoom dentro dos mapas funcionará por padrão no centro da janela. E assim acabou.

À esquerda e logo abaixo da mira - UILabel. Está planejado exibir um endereço legível lá. Botão direito com UIActivityIndicator. O ponto é que, até que as coordenadas do usuário cheguem, ele "gira" e o botão fica escuro e desativado. Ao clicar no botão com as coordenadas recebidas do usuário, devolvemos a cruz para ele. Esta é uma indicação da posição, começando pela posição do usuário.

Na parte inferior está o botão "Selecionar ponto". Ao clicar, a mágica da lógica de negócios ocorre:

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

Viva! Discutimos a fase preparatória!

Prosseguimos para o MRSG.

Abaixo está um texto em formato de tabela que reflete pessoalmente minhas avaliações (com elementos difusos) dos módulos de mapas internos mais famosos (na época, eu não sabia sobre www.openstreetmap.org , obrigado, daglob ).



“Como a tarefa é simples, eu uso o Yandex.Maps. Eles são lindos, ágeis ... "- pensei.

Se alguém estiver interessado em como configurar isso, escreva um miniprojeto , mas se você for muito preguiçoso - tech.yandex.ru/maps/mapkit/?from=mapsapi , siga o caminho do meu caminho. Começar é fácil.

O fato é que a documentação é apresentada neste formulário:



Preste atenção na escassa descrição e na enorme lista de objetos à esquerda. Condene sua perna.
"Provavelmente o projeto de teste responderá às minhas perguntas." Bem, bem.

Aqui está essa fera. github.com/yandex/mapkit-ios-demo
Não vi soluções para minha tarefa trivial lá.
- Tudo bem - acho - tenho experiência suficiente, se não sou desenvolvedor.



Eu montei um projeto de teste e observei o recurso de personalizar o marcador do usuário por um longo tempo.

Pontos-chave:
Existe um objeto:

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

"Tudo parece ser lógico", você diz. Mas não. Métodos alternativos:

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

temos a oportunidade de chegar ao point.lat e point.long de maneiras extremamente difíceis.
Por exemplo, assim:

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

O tempo de espera para carregar coordenadas varia com essa abordagem de 2 a 50 segundos.

"Você está errado, deve haver um LocationManager focado", eu disse a mim mesma. Como se viu depois - de fato, esse "amigo" está na documentação ... MAS ONDE HÁ UM EXEMPLO COM ELE?!?
No projeto de amostra, não há exemplo da aplicação desse gerente:



- Bem, documentação, apenas você e eu resta.
- Sim, não há problemas, "inovador", aproveite:



Assinamos o UIViewController no protocolo (espero que não haja necessidade de explicar mais nada aqui, bem, sério, pessoal):

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

E ...



1-15 segundos, CARL! 15! Às vezes funciona mais rápido com a opção anterior! Como assim ??
Yandex, que piada? Tanto tempo para tentar tudo e obter esse resultado - bem, geralmente é triste.

Eu pensei, pensei ... Bem, na verdade não é o mesmo. Dê a uma pessoa um controlador com cartões e deixe-a estupor ao mudar para ele por mais de 4 segundos - isso é suicídio para o aplicativo. Ninguém esperará mais de 5 segundos com total confiança de que isso é confortável (se você não acredita em mim, ouça os relatórios de Vitaliy Fridman sobre UI / UX).

Pensei mais ... e a seguinte emoção foi essa:


Quem quer com som - www.youtube.com/watch?v=pTZaNHZGsQo

A receita para o sucesso foi esta:
Pegue um quilo ... CLLocationManager e YMKLocationManager e ... faça com que eles trabalhem juntos.



Essa junção ... "trabalho" se parece com isso:

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


... e o pilaf está pronto

O resultado da velocidade de obter o ponto do usuário: um pouco mais de 0 segundos.

O próprio caso em que (na minha opinião) ao resolver um problema trivial, a oposição das partes nativas e incorporadas era assim:



Geocoder *
Como bônus, mostrarei a implementação do geocoder.
Geocoder Yandex usado 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 } } 

Este recurso ajuda muito a converter a resposta do servidor no modelo: app.quicktype.io

O resultado do trabalho visualmente é o seguinte:



Resumindo o acima:
colegas, o artigo foi escrito para que, ao resolver um problema desse tipo, você não gaste tanto tempo quanto eu, e escolha um caminho diferente ou siga-o rapidamente.

Eu gostaria de ver críticas construtivas e / ou soluções corretas alternativas.

Se o artigo se mostrou útil para você, corrija-me com uma classificação de Lois e, de preferência, verifique a versão original desta história de uma nova maneira.

Todo o sucesso criativo e bom humor!

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


All Articles