Usando os locais nativos da Apple

imagem Alguns anos atrás, eu estava trabalhando em um projeto que deveria ter recursos semelhantes ao Snapchat e, ao mesmo tempo, ser um aplicativo semelhante ao Instagram, temático. O projeto foi desenvolvido apenas para uma plataforma - iOS. Naturalmente, durante o desenvolvimento do principal recurso - a publicação de fotos, o cliente subitamente quis adicionar a capacidade de marcar o local em que a foto foi tirada. Na maioria dos casos, muitos se lembram imediatamente da API do Google Places e do Facebook, mas o cliente não ficou satisfeito com o fato de que essas soluções tinham certos limites. Portanto, após uma revisão adicional, foi encontrada outra alternativa da Apple - o CLPlacemark , que era gratuito e, na época, na documentação, não havia menção de limites para o uso diário. Como o desenvolvimento para outras plataformas não foi originalmente planejado, parecia uma opção muito adequada.

A documentação da Apple mostra que o CLPlacemark pode fornecer muitos detalhes sobre o ponto, e o CLGeocoder também possui um método que permite retornar facilmente a matriz do CLPlacemark com os dados necessários pelo nome do local. Como se viu, tudo não funciona tão bem.

O código fonte se parecia com isso:

import CoreLocation let geocoder = CLGeocoder() func findPlace(name: String) { geocoder.geocodeAddressString(name) { placemarks, error in print(placemarks) } } findPlace(name: “New”) 

Nesse cenário simples, o geocoder sempre retorna uma matriz CLPlacemark, mas o problema é que essa matriz nunca contém mais de um elemento. Como resultado, em toda a tela, onde era esperada uma grande lista de canais como: Nova York, Nova Zelândia, New Balance Store etc., recebi apenas um elemento que nem sempre era relevante para o que inseri.

Depois de uma luta malsucedida com o CLGeocoder, meu colega me disse: "E você não pensou em procurar, talvez o MapKit tenha uma oportunidade semelhante?" Como se viu, o MapKit possui o MKLocalSearch , onde podemos obter o array MKPlacemark , que é herdado do CLPlacemark. O circuito parecia bastante funcional, então comecei a tentar esta abordagem:

 import MapKit let request = MKLocalSearchRequest() var localSearch: MKLocalSearch? func findPlace(name: String) { request.naturalLanguageQuery = text localSearch = MKLocalSearch(request: request) localSearch?.start { (searchResponse, _) in guard let items = searchResponse?.mapItems else { return } print(items) } } findPlace(name: “New) 

Resultado

Nesse caso, recebi uma matriz com 10 elementos CLPlacemark em resposta. Esse resultado parecia mais aceitável, pois, como resultado, foi fornecida uma lista suficiente. Mas longe de sempre, quando você começa a digitar o nome de qualquer uma das instituições localizadas nas proximidades, ele mostra imediatamente o resultado desejado. Por exemplo, a Domino's Pizza está ao meu lado. Gostaria que, quando digitasse essa consulta em uma linha, antes de mais nada, os estabelecimentos fossem o mais próximo possível de mim.

Comecei a estudar com base no que a matriz é formada e como ela pode ser melhorada. Eu identifiquei várias coisas que podem influenciar a escolha dos parâmetros:

  1. O endereço IP a partir do qual a solicitação é feita à Apple. Quando a VPN foi ativada, os objetos nos resultados já estavam mais próximos do local do servidor VPN.
  2. A localização atual do usuário. Se as coordenadas do usuário atual forem enviadas para a solicitação, os resultados serão muito mais precisos.
  3. O idioma do sistema do dispositivo.

Exemplo de VPN

Exemplos de VPN
Nova iorque

Toronto

Kiev

Londres

Frankfurt am Main


É possível que haja outros fatores que possam afetar os resultados da pesquisa, mas isso foi suficiente para que eu alcançasse o resultado desejado.

O curso de desenvolvimento adicional foi o uso da localização atual do dispositivo.

 import UIKit import MapKit import CoreLocation final class ViewController: UIViewController, CLLocationManagerDelegate { private let locationManager = CLLocationManager() private let request = MKLocalSearch.Request() private var localSearch: MKLocalSearch? private var region = MKCoordinateRegion() override func viewDidLoad() { super.viewDidLoad() if CLLocationManager.locationServicesEnabled() { locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyBest locationManager.requestWhenInUseAuthorization() locationManager.startUpdatingLocation() } } func searchPlace(_ place: String) { localSearch?.cancel() request.naturalLanguageQuery = place request.region = region localSearch = MKLocalSearch(request: request) localSearch?.start { [weak self] response, error in let mapItems = response.mapItems //     MKMapItem } } // MARK: - CLLocationManagerDelegate func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let lastLocation = locations.last else { return } let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5) region = MKCoordinateRegion(center: lastLocation.coordinate, span: span) } } 

Resultado
A localização atual do dispositivo - Madrid, provedor de Internet Vodafone ES

No método delagate didUpdateLocations, criamos MKCoordinateSpan . Se entendi corretamente a documentação da Apple, quanto menor o valor que definimos delta de latitude / longitude, mais estreita (e mais precisa) nossa região atual será indicada, pois é uma espécie de zoom em nossas coordenadas atuais no MapKit.
Depois disso, de fato, a prioridade da extradição mudou e me mostrou principalmente os lugares que estão ao meu lado.

Resta apenas tornar os nomes da lista mais bonitos. Como algumas vezes, algumas propriedades do CLPlacemark podem ter o mesmo nome, no final, não parecerá muito agradável: Nova York, Nova York, NY. Para fazer isso, você precisa criar uma estrutura separada, que formará um nome bonito na lista.

 import Foundation import MapKit struct Placemark { let location: String init(item: MKMapItem) { var locationString: String = "" if let name = item.name { locationString += "\(name)" } if let locality = item.placemark.locality, locality != item.name { locationString += ", \(locality)" } if let administrativeArea = item.placemark.administrativeArea, administrativeArea != item.placemark.locality { locationString += ", \(administrativeArea)" } if let country = item.placemark.country, country != item.name { locationString += ", \(country)" } location = locationString } } 

Então, já em resposta à pesquisa, podemos mapear facilmente o CLPlacemark na estrutura criada e passá-lo para a lista.

 localSearch?.start { [weak self] searchResponse, error in guard let items = searchResponse?.mapItems else { return } //  CLPlacemark    let placemarks = items.map { Placemark(item: $0) } } 

Agora, os resultados parecem mais elegantes e já podem ser usados ​​no projeto para marcar os locais visitados.

Uma das principais desvantagens é que você pode usar esta solução apenas se o projeto for aprimorado para iOS / Mac OS. Se o projeto envolver desenvolvimento para outras plataformas, eu recomendaria o uso de uma solução do Google ou do Facebook . Além disso, nem todos os locais são definidos idealmente em todas as regiões.

Você pode ver o código final do projeto no repositório .

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


All Articles