Совсем недавно я опубликовал статью, буквально пропитанную любовью к Яндекс.Картам. Поэму. Оду. Вот, собственно, она habr.com/ru/post/479102
Удостоверившись, что среди программистов мало любителей стихов, я все же решил осветить ситуацию более «по-ХАБРовски». Ловите кучку кода, размышлений и скринов. Поехали.
Задача тривиальна: при входе на контроллер с картами нужно сразу «отзуммиться» на точку пользователя (бонус: надо бы еще получить адрес в читаемом виде вместе со всеми доступными атибутами).
Врубаем аналитика: «Разделяй и властвуй».
Для достижения поставленной цели необходимо решить ряд технических и бизнес-задач, а именно:
0. Перейти на контроллер с потенциальным модулем работы с геопозицией (МРСГ), callBack-ами и т.д.
1. МРСГ
1.1 Реализовать МРСГ
1.2 Запустить МРСГ
1.2.1 Получить координаты пользователя
1.2.2 Отзуммиться на него
2*. Получить адрес позиции в читаемом формате.
Переход на контроллер (VIPER + Configurator)
extension AddPresenter: AddPresentationLogic { // ... func openMap(_ delegate: YandexMapSetPointViewControllerProtocol?) { router.routeTo(target: .addPlace(delegate)) } // ... }
Делегат имеет только одну функцию для того, чтобы при указании нужной точки можно было вернуть ее по протоколу в контроллер, вызывающий контроллер с картой:
protocol YandexMapSetPointViewControllerProtocol { func didSelectPoint(_ place: Place) }
В делегат мы отправляем управляющую часть вызывающего кода. Тут как бы все понятно.
Вот так незатейливо мы переходим на контроллер…

Перекрестие в центре находится точно в центре контроллера и в центре UIView карт. Предположение, что зумминг внутри карт будет работать по умолчанию в центр окна. Так и оказалось.
Левее и чуть ниже перекрестия — UILabel. Туда планируется выводить читаемы адрес. Справа кнопка с UIActivityIndicator. Смысл в том, что, пока координаты пользователя не пришли, он «вращается» и кнопка затемнена и disabled. По нажатию на кнопку с полученными координатами пользователя мы возвращаем перекрестие на него. Этакое указание позиции, отталкиваясь от позиции пользователя.
Внизу кнопка «Выбрать точку». По нажатию происходит магия бизнес-логики:
@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) }
Ура! Мы обсудили подготовительный этап!
Приступаем к МРСГ.
Ниже приведен табличнообразно-отформатированный текст, отражающий лично мои оценки (с элементами нечеткости) самых известных (на тот момент я не знал про www.openstreetmap.org, спасибо, daglob) встраиваемых модулей карт.

«Так как задача простая, использую-ка Яндекс.Карты. Они красивые, шустрые…» — подумал я.
Если кому интересно, как это настраивать, — напишите минипроект, но, если лень — tech.yandex.ru/maps/mapkit/?from=mapsapi, вставай на рельсы моего пути. Начать несложно.
Факт в том, что документация представлена в таком виде:

Обратите внимание на скудное описание и огромный список объектов слева. Чёрт ногу сломит.
«Наверное, тестовый проект ответит на мои вопросы». Ну ну.
Вот этот зверь. github.com/yandex/mapkit-ios-demo
Решения для своей тривиальной задачи я там не увидел.
— Ладно, — думаю — опыта мне хватит, я ли не разраб.

Собрал тестовый проект, долго смотрел на фичу кастомизации маркера пользователя.
Ключевые моменты:
Есть объект:
@IBOutlet weak var mapView: YMKMapView! // и YMKUserLocationObjectListener - слушатель, получающий сведения
«Вроде бы все логично» скажете вы. Ан нет. Проимплементив методы:
func onObjectAdded(with view: YMKUserLocationView) {} func onObjectRemoved(with view: YMKUserLocationView) {} func onObjectUpdated(with view: YMKUserLocationView, event: YMKObjectEvent) {}
мы получаем возможность добраться к point.lat и point.long крайне непростыми путями.
Например, вот так:
userLocation = YMKPoint(latitude: view.pin.geometry.latitude, longitude: view.pin.geometry.longitude)
Время ожидания подгрузки координат варьируется при таком подходе от 2 до 50 секунд.
«Ты неправ, там должен быть целенаправленный LocationManager» — сказал я сам себе. Как оказалось далее — действительно, такой «друг» есть в документации… НО ГДЕ ПРИМЕР С НИМ?!?
В примерном проекте примера применения такого менеджера нет:

— Ну ладно, документация, остались только мы с тобой.
— Да нет проблем, «новатор», наслаждайся:

Подписываем UIViewController на протокол (надеюсь, не надо тут ничего дополнительно пояснять, ну реально, ребяяяят):
// 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) {} }
И…

1-15 секунд, КАРЛ! 15! Иногда быстрее отрабатывает предыдущий вариант! Как так-то??
Яндекс, ну что за прикол? Столько времени потратить, чтобы это все попробовать, так еще и получить такой результат — ну это вообще грустно.
Думал я, думал… Ну не реально же. Дать человеку контроллер с картами и ввести его в ступор при переходе на него более, чем на 4 секунды — это самоубийство для приложения. Никто не будет ждать больше 5 секунд с полной уверенностью, что это комфортно (если не верите мне — послушайте доклады Виталия Фридмана по UI/UX).
Подумал еще… и следующая эмоция была такой:

Кто хочет со звуком — www.youtube.com/watch?v=pTZaNHZGsQo
Рецепт успеха был такой:
Берем кило … CLLocationManager и YMKLocationManager и… заставляем их работать вместе.

Выглядит эта совместная… «работа» примерно так:
// 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) } }
… и плов готов
Результат скорости получения точки пользователя: чуть больше, чем 0 секунд.
Тот самый случай, когда (на мой взгляд) при решении тривиальной задачи противостояние нативной и встраиваемой части выглядело так:

Использовал Геокодер Яндекса 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 } }
Для преобразования ответа от сервера в модель очень помогает этот ресурс: app.quicktype.io
Результат работы визуально такой:

Резюмируя вышесказанное:
коллеги, статья написана для того, чтобы при решении подобной задачи вы не тратили столько же времени, сколько потратил я и либо выбрали другой путь, либо прошли его быстро.
Хотелось бы увидеть конструктивную критику и/или альтернативные правильные решения.
Если статья оказалась для вас полезной — поправьте мне рейтинг лойсом и, желательно, зацените по-новому первоначальный вариант этого повествования.
Всем творческих успехов и положительного настроения!
ссылка на оригинал статьи https://habr.com/ru/post/479432/

Добавить комментарий