![image](https://habrastorage.org/webt/wg/mk/7p/wgmk7pk9kfkatujkomconugqpg8.png)
Документация Apple показывает, что CLPlacemark может предоставлять много подробностей о точке, а так же есть метод у CLGeocoder который позволяет легко, по названию места, вернуть массив CLPlacemark с нужными данными. Как оказалось, работает это все не настолько радужно.
Исходный код выглядел примерно так:
import CoreLocation
let geocoder = CLGeocoder()
func findPlace(name: String) {
geocoder.geocodeAddressString(name) { placemarks, error in
print(placemarks)
}
}
findPlace(name: “New”)
При таком простом раскладе, geocoder всегда возвращает массив CLPlacemark, но загвоздка оказалась в том, что этот массив никогда не содержит более одного элемента. В итоге, на весь экран, где ожидался большой список плейсментов вроде: New York, New Zeland, Магазин New Balance и т.п, я получал только один какой-то элемент, который даже не всегда был релевантным тому, что я вводил.
После некоторой безуспешной борьбы с CLGeocoder мне коллега подсказал: “А ты не думал попробовать посмотреть, может у MatKit есть подобная возможность?” Как оказалось, MapKit имеет MKLocalSearch, где мы можем получать массив MKPlacemark, который наследуется от CLPlacemark. Схема, выглядела вполне рабочей, поэтому я начал пробовать этот подход:
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)
Результат![](https://habrastorage.org/webt/ca/lz/7l/calz7lugmo8zxu97zizbenvnqbi.jpeg)
![](https://habrastorage.org/webt/ca/lz/7l/calz7lugmo8zxu97zizbenvnqbi.jpeg)
В данном случае, я получал в ответ массив с уже 10 элементами CLPlacemark. Такой результат выглядел более приемлемым, потому что в результате предоставлялся достаточный перечень. Но далеко не всегда, при начале ввода названия какого-либо из заведений, расположенных рядом, оно сразу показывало нужный результат. К примеру, рядом со мной находится Domino's Pizza. Мне хотелось, что бы когда я ввожу в строке такой запрос, в первую очередь получать заведения как можно ближе ко мне.
Я принялся изучать, на основе чего формируется массив, и как его можно улучшить. Я определил несколько вещей, которые могут влиять на выбор параметров:
- IP адрес с которого делается запрос к Apple. При включенном VPN объекты в выдаче были уже более приближены к локации VPN сервера.
- Текущая локация пользователя. Если будут передаваться в запрос текущие координаты пользователя, то результаты получатся намного точнее.
- Системный язык устройства.
Пример без VPN![](https://habrastorage.org/webt/t1/zt/os/t1ztoss0yanftclwrzckdemegda.jpeg)
![](https://habrastorage.org/webt/t1/zt/os/t1ztoss0yanftclwrzckdemegda.jpeg)
Примеры с VPN
New York
![](https://habrastorage.org/webt/c_/yj/bp/c_yjbpjus2nwmag4dm33cbotuai.jpeg)
Toronto
![](https://habrastorage.org/webt/qv/by/rf/qvbyrf2bv3xlwjgf7f7dbn6d--i.jpeg)
Kyiv
![](https://habrastorage.org/webt/br/7-/uj/br7-uj3dnanlwpoxfu5edxigfb4.jpeg)
London
![](https://habrastorage.org/webt/vt/4i/yt/vt4iyt0qhs0bcltjduaf53gz3pk.jpeg)
Frankfurt
![](https://habrastorage.org/webt/he/8x/hd/he8xhd7wg_er5xusctuvya1w138.jpeg)
![](https://habrastorage.org/webt/c_/yj/bp/c_yjbpjus2nwmag4dm33cbotuai.jpeg)
Toronto
![](https://habrastorage.org/webt/qv/by/rf/qvbyrf2bv3xlwjgf7f7dbn6d--i.jpeg)
Kyiv
![](https://habrastorage.org/webt/br/7-/uj/br7-uj3dnanlwpoxfu5edxigfb4.jpeg)
London
![](https://habrastorage.org/webt/vt/4i/yt/vt4iyt0qhs0bcltjduaf53gz3pk.jpeg)
Frankfurt
![](https://habrastorage.org/webt/he/8x/hd/he8xhd7wg_er5xusctuvya1w138.jpeg)
Вполне возможно, есть и другие факторы, которые могут влиять на результаты поиска, но мне было этого достаточно для достижения нужного результата.
Дальнейшим ходом разработки было использование текущей локации девайса.
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)
}
}
Результат
Текущая локация девайса — Мадрид, интернет провайдер Vodafone ES
![](https://habrastorage.org/webt/rb/fp/5u/rbfp5uei98rnxz9iqfcso_nm2-y.jpeg)
![](https://habrastorage.org/webt/rb/fp/5u/rbfp5uei98rnxz9iqfcso_nm2-y.jpeg)
В методе делагата didUpdateLocations, мы создаем MKCoordinateSpan. Если я правильно понял документацию Apple, то чем меньше значение мы ставим latitude/longitude Delta, тем более узким (и точным) будет указываться наш текущий регион, поскольку он является своеобразным зумом на наших текущих координатах в MapKit.
После этого, действительно, приоритет выдачи изменился и мне показывало в первую очередь те места, которые рядом со мной.
Осталось только сделать более красивыми названия в списке. Так как иногда, некоторые свойства у CLPlacemark могут иметь одинаковые названия, в итоге это будет не очень красиво выглядеть: New York, New York, NY. Для этого необходимо создать отдельную Структуру, которая будет формировать красивое название в списке.
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
}
}
Тогда уже в ответе на поиск, мы можем легко смапить CLPlacemark в созданную структуру и передать ее в список.
localSearch?.start { [weak self] searchResponse, error in
guard let items = searchResponse?.mapItems else {
return
}
// Конвертируем CLPlacemark в созданную структуру
let placemarks = items.map { Placemark(item: $0) }
}
Теперь выдача смотрится более элегантно и ее можно уже использовать в проекте для отметок своих посещенных локаций.
Один из основных недостатков заключается в том, что использовать данное решение можно только если проект заточен под iOS/Mac OS. Если же проект подразумевает разработку для других платформ, я бы рекомендовал использовать решение от Google или Facebook. Так же, не во всех регионах идеально определяются все локации.
Итоговый код проекта вы можете посмотреть в репозитории.