Pull to refresh

Интеграция Yandex MapKit в SwiftUI проект. Часть 1

Level of difficultyMedium
Reading time4 min
Views3.7K

Всех приветствую.

Совсем недавно поступила задача заменить стандартные карты от Apple на Яндекс карты. Ниже немного о Yandex MapKit.

Yandex MapKit — это кроссплатформенная библиотека, которая позволяет использовать возможности Яндекс.Карт в мобильных приложениях для iOS и Android.

Основная проблема заключалась в том, что библиотека Yandex MapKit создана для UIKit, но наша цель поставить все это дело на SwiftUI. Поэтому прибегаем к старой доброй пикче:

Будем считать, что вы получили ключ у Яндекса и установили библиотеку в проект, для ознакомления с установкой прикрепляю ссылку.

Первым делом создаем класс AppDelegate, подробнее как реализовать этот класс в SwiftUI проекте здесь.

Затем идем в таргет проекта -> Info и прописываем Privacy

В классе AppDelegate импортируем библиотеку Яндекса и устанавливаем ваш API ключ из кабинета разработчика Яндекса

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    YMKMapKit.setApiKey("Ваш API-ключ")
    YMKMapKit.sharedInstance()
}

Создадим класс LocationManager, в который импортируем следующее:

import Foundation
import CoreLocation
import YandexMapsMobile
import Combine

LocationManager будет отвечать за всю логику взаимодействия с картами, объявим в нем саму карту и приватную переменную manager, которая наследуется от класса CLLocationManager, для работы с местоположением пользователя

class LocationManager: NSObject{

    lazy var map : YMKMap = {
        return mapView.mapWindow.map
    }()

    private let manager = CLLocationManager()

    let mapView = YMKMapView(frame: CGRect.zero)
    override init(){
        super.init()
    }
}

Унаследуем класс от протокола CLLocationManagerDelegate и в init() подпишем делегат объявленного менеджера и вызовем у него функции нахождения геопозиции.

class LocationManager: NSObject, CLLocationManagerDelegate
 override init(){
        super.init()
        manager.delegate = self
        manager.startUpdatingLocation()
    }

Реализуем в классе LocationManager функцию делегата для проверки статуса и начала использования геопозиции

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if status == .authorizedWhenInUse {
            self.manager.startUpdatingLocation()
        }
    }

Затем создадим переменную в которую будем записывать последнее местоположение пользователя:

@Published var lastUserLocation: CLLocation? = nil

Реализуем функцию протокола для того, чтобы слушать изменение локации пользователя и записывать в переменную:

  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
      self.lastUserLocation = locations.last
 }

Приступим написанию UI части. Создадим SwiftUIView с названием MapView и обернем в ZStack. Объявим экземпляр класса LocationManager

  @ObservedObject var locationManager = LocationManager()

Так как Yandex MapKit работает с UIKit, создадим структуру YandexMapView и наследуемся от протокола UIViewRepresentable.

Объявим EnviromentObject переменную от класса LocationManager и реализуем функцию makeUIView(), в котором вернем как View ранее созданные mapView в классе LocationManager

import SwiftUI
import YandexMapsMobile
import Combine

struct YandexMapView: UIViewRepresentable {
    @EnvironmentObject var locationManager : YaLocationManager
    func makeUIView(context: Context) -> YMKMapView {
        return locationManager.mapView
    }  
    func updateUIView(_ mapView: YMKMapView, context: Context) {}
}

Вернемся в LocationManager и создадим приватную функцию centerMapLocation, которая на вход будет принимать параметр target, в который будем подавать долготу и широту, и параметр map, который принимает Яндекс карту.

  func centerMapLocation(target location: YMKPoint?, map: YMKMapView) {
         guard let location = location else { print("Failed to get user location"); return }
         map.mapWindow.map.move(
             with: YMKCameraPosition(target: location, zoom: 18, azimuth: 0, tilt: 0),
             animationType: YMKAnimation(type: YMKAnimationType.smooth, duration: 0.5)
         )
     }

И теперь реализуем функцию для показа текущей геопозиции пользователя, она будет брать lastUserLocation и передавать в метод centerMapLocation, описанный выше.

 func currentUserLocation(){
        if let myLocation = lastKnownLocation {
             centerMapLocation(target: YMKPoint(latitude: myLocation.coordinate.latitude, longitude: myLocation.coordinate.longitude), map: mapView)
      }
 }

Объявим в ZStack YandexMapView и передадим ей объявленный класс

 ZStack{
    YandexMapView()
    .edgesIgnoringSafeArea(.all)
    .environmentObject(locationManager)
}

Осталось лишь в OnAppear вызвать метод currentUserLocation

.onAppear{
        locationManager.currentUserLocation()
}

В результате получаем, работающую Яндекс Карту в вашем SwiftUI проекте

Результат

Ниже прикрепляю код всех классов:

LocationManager
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject{
    
    private let manager = CLLocationManager()
    let mapView = YMKMapView(frame: CGRect.zero)
    @Published var lastUserLocation: CLLocation? = nil
    lazy var map : YMKMap = {
        return mapView.mapWindow.map
    }()
    
    override init() {
        super.init()
        self.manager.delegate = self
    }
    
    func currentUserLocation(){
        if let myLocation = lastUserLocation {
            centerMapLocation(target: YMKPoint(latitude: myLocation.coordinate.latitude, longitude: myLocation.coordinate.longitude), map: mapView )
        }
    }
    
    func centerMapLocation(target location: YMKPoint?, map: YMKMapView) {
        
        guard let location = location else { print("Failed to get user location"); return }
        
        map.mapWindow.map.move(
            with: YMKCameraPosition(target: location, zoom: 18, azimuth: 0, tilt: 0),
            animationType: YMKAnimation(type: YMKAnimationType.smooth, duration: 0.5)
        )
    }
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if status == .authorizedWhenInUse {
            self.manager.startUpdatingLocation()
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // Notify listeners that the user has a new location
        self.lastUserLocation = locations.last
    }
}

MapView
struct MapView: View {
    @ObservedObject var locationManager = LocationManager()
    var body: some View {
        ZStack{
            YandexMapsView().edgesIgnoringSafeArea(.all).environmentObject(locationManager)
        }.onAppear{
            locationManager.currentUserLocation()
        }
    }
}

YandexMapView
struct YandexMapView: UIViewRepresentable {
    @EnvironmentObject var locationManager : YaLocationManager
    func makeUIView(context: Context) -> YMKMapView {
        return locationManager.mapView
    }
         
    func updateUIView(_ mapView: YMKMapView, context: Context) {}
}

В следующей части будет реализован поиск по адресу, позиция на карте, выбранного адреса и обновление адреса по координатам из центра камеры.

Всем спасибо за внимание.

Tags:
Hubs:
Total votes 3: ↑3 and ↓0+3
Comments1

Articles