Начало работы
Для работы с SDK нужно создать специальный объект Container, который будет хранить все сущности, связанные с картой.
Чтобы его создать, нужно указать набор ключей доступа к SDK в виде структуры APIKeys.
// Набор ключей для доступа к сервисам.
guard let apiKeys = APIKeys(directory: "Directory API key", map: "SDK key") else {
fatalError("Указанные API-ключи недействительны.")
}
// Создание контейнера для доступа к возможностям SDK.
let sdk = DGis.Container(apiKeys: apiKeys)
Обратите внимание, DGis.Container может быть создан только в единственном экземпляре.
Дополнительно можно указать настройки журналирования (LogOptions) и настройки HTTP-клиента (HTTPOptions), такие как время ожидания ответа и кеширование.
// Настройки журналирования.
let logOptions = LogOptions(osLogLevel: .info)
// Настройки HTTP-клиента.
let httpOptions = HTTPOptions(timeout: 5, cacheOptions: nil)
// Сервисы геопозиционирования.
let positioningServices: IPositioningServicesFactory = CustomPositioningServicesFactory()
// Настройки сбора анонимной статистики использования.
let dataCollectionOptions = DataCollectionOptions(dataCollectionStatus: .agree)
// Создание контейнера.
let sdk = DGis.Container(
apiKeys: apiKeys,
logOptions: logOptions,
httpOptions: httpOptions,
positioningServices: positioningServices,
dataCollectionOptions: dataCollectionOptions
)
Начало работы с версии 4.x
Сначала нужно обратиться в техническую поддержку 2ГИС для получения нового ключа. Обязательно нужно указать appId
приложения, для которого будет создан ключ.
Для работы с SDK нужно создать специальный объект Container, который будет хранить все сущности, связанные с картой.
Чтобы его создать, нужно указать путь до файла ключа через объект структуры ApiKeyOptions. При указании ApiKeyOptions.default файл должен быть добавлен в корень приложения.
// Файл ключа для доступа к сервисам.
let apiKeyOptions = ApiKeyOptions(apiKeyFile: File(path: "Path to key info file"))
// Создание контейнера для доступа к возможностям SDK.
let sdk = DGis.Container(apiKeyOptions: apiKeyOptions)
Обратите внимание, DGis.Container может быть создан только в единственном экземпляре.
Дополнительно можно указать настройки журналирования (LogOptions) и настройки HTTP-клиента (HTTPOptions), такие как время ожидания ответа и кеширование.
// Настройки журналирования.
let logOptions = LogOptions(osLogLevel: .info)
// Настройки HTTP-клиента.
let httpOptions = HTTPOptions(timeout: 5, cacheOptions: nil)
// Сервисы геопозиционирования.
let positioningServices: IPositioningServicesFactory = CustomPositioningServicesFactory()
// Настройки сбора анонимной статистики использования.
let dataCollectionOptions = DataCollectionOptions(dataCollectionStatus: .agree)
// Создание контейнера.
let sdk = DGis.Container(
apiKeyOptions: apiKeyOptions,
logOptions: logOptions,
httpOptions: httpOptions,
positioningServices: positioningServices,
dataCollectionOptions: dataCollectionOptions
)
Создание карты
Чтобы создать карту, нужно вызвать метод makeMapFactory() и передать настройки карты в виде структуры MapOptions.
В настройках важно указать корректное для устройства значение PPI. Его можно найти в спецификации устройства.
Кроме этого в настройках можно указать начальную позицию камеры, границы масштабирования и другие параметры.
// Настройки карты.
var mapOptions = MapOptions.default
// Значение PPI для устройства.
mapOptions.devicePPI = devicePPI
// Создание фабрики объектов карты.
let mapFactory: PlatformMapSDK.IMapFactory = sdk.makeMapFactory(options: mapOptions)
Получить слой карты можно через свойство mapView
. Контроллер карты доступен через свойство map
(см. класс Map).
// Слой карты.
let mapView: UIView & IMapView = mapFactory.mapView
// Контроллер карты.
let map = mapFactory.map
Общие принципы работы
Работа с отложенными результатами
Некоторые методы SDK (например те, которые обращаются к удаленному серверу) возвращают отложенные результаты (объект Future). Для работы с ними нужно создать обработчик получения данных и обработчик ошибок. Обработать результат в главной очереди можно с помощью DispatchQueue.
Пример получения объекта из справочника:
// Создание объекта для поиска по справочнику.
let searchManager = SearchManager.createOnlineManager(context: sdk.context)
// Получение объекта из справочника по идентификатору.
let future = searchManager.searchByDirectoryObjectId(objectId: object.id)
// Обработка результата поиска в главной очереди.
// Сохраняем результат вызова, так как его удаление отменяет обработку.
self.searchDirectoryObjectCancellable = future.sink(
receiveValue: {
[weak self] directoryObject in
guard let directoryObject = directoryObject else { return }
DispatchQueue.main.async {
self.handle(directoryObject)
}
},
failure: { error in
DispatchQueue.main.async {
self.handle(error)
}
}
)
Для упрощения работы можно создать расширение:
extension DGis.Future {
func sinkOnMainThread(
receiveValue: @escaping (Value) -> Void,
failure: @escaping (Error) -> Void
) -> DGis.Cancellable {
self.sink(on: .main, receiveValue: receiveValue, failure: failure)
}
func sink(
on queue: DispatchQueue,
receiveValue: @escaping (Value) -> Void,
failure: @escaping (Error) -> Void
) -> DGis.Cancellable {
self.sink { value in
queue.async {
receiveValue(value)
}
} failure: { error in
queue.async {
failure(error)
}
}
}
}
self.searchDirectoryObjectCancellable = future.sinkOnMainThread(
receiveValue: {
[weak self] directoryObject in
guard let directoryObject = directoryObject else { return }
self.handle(directoryObject)
},
failure: { error in
self.handle(error)
}
)
Можно также использовать Combine:
// Создание Combine.Future из DGis.Future.
extension DGis.Future {
func asCombineFuture() -> Combine.Future<Value, Error> {
Combine.Future { [self] promise in
// Удерживаем ссылку на Cancellable, пока не будет вызван обработчик
// Combine.Future не позволяет конфигурировать отмену напрямую
var cancellable: DGis.Cancellable?
cancellable = self.sink {
promise(.success($0))
_ = cancellable
} failure: {
promise(.failure($0))
_ = cancellable
}
}
}
}
// Создание Combine.Future.
let combineFuture = future.asCombineFuture()
// Обработка результата поиска в главной очереди.
combineFuture.receive(on: DispatchQueue.main).sink {
[weak self] completion in
switch completion {
case .failure(let error):
self?.handle(error)
case .finished:
break
}
} receiveValue: {
[weak self] directoryObject in
self?.handle(directoryObject)
}.store(in: &self.subscriptions)
Работа с потоками значений
Некоторые объекты SDK предоставляют потоки значений, которые можно обработать, используя механизм каналов: на поток можно подписаться, указав функцию-обработчик данных, и отписаться, когда обработка данных больше не требуется. Для работы с потоками значений используется класс Channel.
Пример подписки на изменение видимой области карты (поток новых прямоугольных областей):
// Выбираем канал (прямоугольники видимой области карты).
let visibleRectChannel = map.camera.visibleRectChannel
// Подписываемся и обрабатываем результаты в главной очереди. Значения будут присылаться при любом изменении видимой области до момента отписки.
// Важно сохранить Cancellable, иначе подписка будет уничтожена.
self.cancellable = visibleRectChannel.sink { [weak self] visibleRect in
DispatchQueue.main.async {
self?.handle(visibleRect)
}
}
Чтобы отменить подписку, нужно вызвать метод cancel()
:
self.cancellable.cancel()
Для упрощения работы можно создать расширение:
extension Channel {
func sinkOnMainThread(receiveValue: @escaping (Value) -> Void) -> DGis.Cancellable {
self.sink(on: .main, receiveValue: receiveValue)
}
func sink(on queue: DispatchQueue, receiveValue: @escaping (Value) -> Void) -> DGis.Cancellable {
self.sink { value in
queue.async {
receiveValue(value)
}
}
}
}
self.cancellable = visibleRectChannel.sinkOnMainThread { [weak self] visibleRect in
self?.handle(visibleRect)
}
Добавление объектов
Для добавления динамических объектов на карту (маркеров, линий, кругов, многоугольников) нужно создать менеджер объектов (MapObjectManager), указав инстанс карты.
// Сохраняем объект в свойство, так как при удалении менеджера исчезают все связанные с ним объекты на карте.
self.objectManager = MapObjectManager(map: map)
Для добавления объектов используются методы addObject() и addObjects(). Для каждого динамического объекта можно указать поле userData
, которое будет хранить произвольные данные, связанные с объектом. Настройки объектов можно менять после их создания.
Для удаления объектов используются методы removeObject() и removeObjects(). Чтобы удалить все объекты, можно использовать метод removeAll().
Маркер
Чтобы добавить маркер на карту, нужно создать объект Marker, указав нужные настройки (MarkerOptions), и передать его в вызов addObject()
менеджера объектов.
Иконку для маркера можно создать с помощью метода make()
фабрики изображений (IImageFactory), используя UIImage, PNG-данные или SVG-разметку.
// Иконка на основе UIImage.
let uiImage = UIImage(systemName: "umbrella.fill")!.withTintColor(.systemRed)
let icon = sdk.imageFactory.make(image: uiImage)
// Иконка на основе SVG-данных.
let icon = sdk.imageFactory.make(svgData: imageData, size: imageSize)
// Иконка на основе PNG-данных (быстрее, чем из UIImage).
let icon = sdk.imageFactory.make(pngData: imageData, size: imageSize)
// Настройки маркера.
let options = MarkerOptions(
position: GeoPointWithElevation(
latitude: 55.752425,
longitude: 37.613983
),
icon: icon
)
// Создание и добавление маркера.
let marker = Marker(options: options)
objectManager.addObject(object: marker)
Чтобы изменить точку привязки иконки (выравнивание иконки относительно координат на карте), нужно указать параметр anchor.
Линия
Чтобы нарисовать на карте линию, нужно создать объект Polyline, указав нужные настройки, и передать его в вызов addObject()
менеджера объектов.
Кроме массива координат для точек линии, в настройках можно указать ширину линии, цвет, пунктир, обводку и другие параметры (см. PolylineOptions).
// Координаты вершин ломаной линии.
let points = [
GeoPoint(latitude: 55.7513, longitude: value: 37.6236),
GeoPoint(latitude: 55.7405, longitude: value: 37.6235),
GeoPoint(latitude: 55.7439, longitude: value: 37.6506)
]
// Настройки линии.
let options = PolylineOptions(
points: points,
width: LogicalPixel(value: 2),
color: DGis.Color.init()
)
// Создание и добавление линии.
let polyline = Polyline(options: options)
objectManager.addObject(object: polyline)
Многоугольник
Чтобы нарисовать на карте многоугольник, нужно создать объект Polygon, указав нужные настройки, и передать его в вызов addObject()
менеджера объектов.
Координаты для многоугольника указываются в виде двумерного массива. Первый вложенный массив должен содержать координаты основных вершин многоугольника. Остальные вложенные массивы не обязательны и могут быть заданы для того, чтобы создать вырез внутри многоугольника (один дополнительный массив - один вырез в виде многоугольника).
Дополнительно можно указать цвет полигона и параметры обводки (см. PolygonOptions).
// Настройки многоугольника.
let options = PolygonOptions(
contours: [
// Вершины многоугольника.
[
GeoPoint(latitude: 55.72014932919687, longitude: 37.562599182128906),
GeoPoint(latitude: 55.72014932919687, longitude: 37.67555236816406),
GeoPoint(latitude: 55.78004852149085, longitude: 37.67555236816406),
GeoPoint(latitude: 55.78004852149085, longitude: 37.562599182128906),
GeoPoint(latitude: 55.72014932919687, longitude: 37.562599182128906)
],
// Координаты выреза внутри многоугольника.
[
GeoPoint(latitude: 55.754167897761, longitude: 37.62422561645508),
GeoPoint(latitude: 55.74450654680055, longitude: 37.61238098144531),
GeoPoint(latitude: 55.74460317215391, longitude: 37.63435363769531),
GeoPoint(latitude: 55.754167897761, longitude: 37.62422561645508)
]
],
color: DGis.Color.init(),
strokeWidth: LogicalPixel(value: 2)
)
// Создание и добавление многоугольника.
let polygon = Polygon(options: options)
objectManager.addObject(object: polygon)
Кластеризация
Для добавления маркеров на карту в режиме кластеризации нужно создать менеджер объектов (MapObjectManager) через MapObjectManager.withClustering(), указав инстанс карты, расстояние между кластерами в логических пикселях, максимальный zoom-уровень формирования кластеров и пользовательскую имплементацию протокола SimpleClusterRenderer. SimpleClusterRenderer используется для кастомизации кластеров в MapObjectManager.
final class SimpleClusterRendererImpl: SimpleClusterRenderer {
private let image: DGis.Image
private var idx = 0
init(
image: DGis.Image
) {
self.image = image
}
func renderCluster(cluster: SimpleClusterObject) -> SimpleClusterOptions {
let textStyle = TextStyle(
fontSize: LogicalPixel(15.0),
textPlacement: TextPlacement.rightTop
)
let objectCount = cluster.objectCount
let iconMapDirection = objectCount < 5 ? MapDirection(value: 45.0) : nil
idx += 1
return SimpleClusterOptions(
icon: self.image,
iconMapDirection: iconMapDirection,
text: String(objectCount),
textStyle: textStyle,
iconWidth: LogicalPixel(30.0),
userData: idx,
zIndex: ZIndex(value: 6),
animatedAppearance: false
)
}
}
self.objectManager = MapObjectManager.withClustering(
map: map,
logicalPixel: LogicalPixel(80.0),
maxZoom: Zoom(19.0),
clusterRenderer: SimpleClusterRendererImpl(image: self.icon)
)
Управление камерой
Для работы с камерой используется объект Camera, доступный через свойство map.camera
.
Перелёт
Чтобы запустить анимацию перелёта камеры, нужно вызвать метод move() и указать параметры перелёта:
position
- конечная позиция камеры (координаты и уровень приближения). Дополнительно можно указать наклон и поворот камеры (см. CameraPosition).time
- продолжительность перелёта в секундах (TimeInterval).animationType
- тип анимации (CameraAnimationType).
Функция move()
возвращает объект Future, который можно использовать, чтобы обработать событие завершения перелета.
// Новая позиция камеры.
let newCameraPosition = CameraPosition(
point: GeoPoint(latitude: 55.752425, longitude: 37.613983),
zoom: Zoom(value: 16)
)
// Запуск перелёта.
let future = map.camera.move(
position: newCameraPosition,
time: 0.4,
animationType: .linear
)
// Получение события завершения перелета.
let cancellable = future.sink { _ in
print("Перелет камеры завершён.")
} failure: { error in
print("Возникла ошибка: \(error.localizedDescription)")
}
Получение состояния камеры
Текущее состояние камеры (находится ли камера в полёте) можно получить, используя свойство state
. См. CameraState для списка возможных состояний камеры.
let currentState = map.camera.state
Подписаться на изменения состояния камеры можно, используя stateChannel.sink
.
// Подписка.
let connection = map.camera.stateChannel.sink { state in
print("Состояние камеры изменилось на \(state)")
}
// Отписка.
connection.cancel()
Получение позиции камеры
Текущую позицию камеры можно получить, используя свойство position
(см. структуру CameraPosition).
let currentPosition = map.camera.position
print("Координаты: \(currentPosition.point)")
print("Приближение: \(currentPosition.zoom)")
print("Наклон: \(currentPosition.tilt)")
print("Поворот: \(currentPosition.bearing)")
Подписаться на изменения позиции камеры (и угла наклона/поворота) можно, используя positionChannel.sink
.
// Подписка.
let connection = map.camera.positionChannel.sink { position in
print("Изменилась позиция камеры или угол наклона/поворота.")
}
// Отписка.
connection.cancel()
Моё местоположение
На карту можно добавить специальный маркер, который будет отражать текущее местоположение устройства. Для этого нужно создать источник данных, вызвав createMyLocationMapObjectSource() и указав контейнер объектов SDK (sdk.context
). Созданный источник нужно передать в метод карты addSource().
// Создание источника данных.
let source = createMyLocationMapObjectSource(
context: sdk.context,
directionBehaviour: MyLocationDirectionBehaviour.followMagneticHeading
)
// Добавление маркера на карту.
map.addSource(source: source)
Чтобы удалить маркер, нужно вызвать метод removeSource(). Список активных источников данных можно получить, используя свойство map.sources
.
map.removeSource(source)
Слой пробок
Для отображения слоя пробок необходимо создать TrafficSource и передать его в метод карты addSource().
let trafficSource = TrafficSource(context: sdk.context)
map.addSource(source: trafficSource)
Построение маршрута
Для того, чтобы проложить маршрут на карте, нужно создать два объекта: TrafficRouter для поиска оптимального маршрута и источник данных RouteMapObjectSource для отображения маршрута на карте.
Чтобы найти маршрут между двумя точками, нужно вызвать метод findRoute(), передав координаты точек в виде объектов RouteSearchPoint и параметры маршрута (RouteSearchOptions). Дополнительно можно указать также список промежуточных точек маршрута (список RouteSearchPoint).
let startPoint = RouteSearchPoint(coordinates: GeoPoint(latitude: 55.759909, longitude: 37.618806))
let finishPoint = RouteSearchPoint(coordinates: GeoPoint(latitude: 55.752425, longitude: 37.613983))
let routeSearchOptions = RouteSearchOptions.car(CarRouteSearchOptions())
let trafficRouter = TrafficRouter(context: sdk.context)
let routesFuture = trafficRouter.findRoute(
startPoint: startPoint,
finishPoint: finishPoint,
routeSearchOptions: routeSearchOptions
)
Вызов вернёт отложенный результат со списком объектов TrafficRoute. Чтобы отобразить найденный маршрут на карте, нужно на основе этих объектов создать объекты RouteMapObject и добавить их в источник данных RouteMapObjectSource.
// Создаём источник данных.
let routeMapObjectSource = RouteMapObjectSource(context: sdk.context, routeVisualizationType: .normal)
map.addSource(source: routeMapObjectSource)
// Ищем маршрут.
self.routeSearchCancellable = routesFuture.sink { routes in
// Ддобавляем их на карту.
for (index, route) in routes.enumerated() {
let routeMapObject = RouteMapObject(
route: route,
isActive: index == 0,
index: RouteIndex(value: UInt64(index)),
displayFlags: nil
)
routeMapObjectSource.addObject(item: routeMapObject)
}
} failure: { error in
print("Не удалось найти маршрут: \(error)")
}
Навигатор
Чтобы создать навигатор, можно использовать готовый элемент интерфейса INavigationView и класс NavigationManager.
Для этого нужно добавить на карту маркер с текущим местоположением и создать слой навигатора с помощью фабрики INavigationViewFactory и класса NavigationManager.
// Создаём фабрику объектов карты.
guard let mapFactory = try? sdk.makeMapFactory(options: .default) else {
return
}
// Добавляем слой карты в иерархию представлений.
let mapView = mapFactory.mapView
mapView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(mapView)
NSLayoutConstraint.activate([
mapView.leftAnchor.constraint(equalTo: containerView.leftAnchor),
mapView.rightAnchor.constraint(equalTo: containerView.rightAnchor),
mapView.topAnchor.constraint(equalTo: containerView.topAnchor),
mapView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
// Добавляем на карту маркер с текущим местоположением.
let locationSource = MyLocationMapObjectSource(
context: sdk.context,
directionBehaviour: .followSatelliteHeading,
controller: createSmoothMyLocationController()
)
let map = mapFactory.map
map.addSource(source: locationSource)
// Создаём NavigationManager.
let navigationManager = NavigationManager(platformContext: sdk.context)
// Добавляем карту в навигатор.
navigationManager.mapManager.addMap(map: map)
// Создаём фабрику UI-компонентов навигатора.
let navigationViewFactory = sdk.makeNavigationViewFactory()
// Создаём с помощью фабрики слой навигатора и размещаем его в иерархии выше слоя карты.
let navigationView = navigationViewFactory.makeNavigationView(
map: map,
navigationManager: navigationManager
)
navigationView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(navigationView)
NSLayoutConstraint.activate([
navigationView.leftAnchor.constraint(equalTo: containerView.leftAnchor),
navigationView.rightAnchor.constraint(equalTo: containerView.rightAnchor),
navigationView.topAnchor.constraint(equalTo: containerView.topAnchor),
navigationView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
// Добавляем обработчик нажатия кнопки закрытия.
navigationView.closeButtonCallback = { [weak navigationManager] in
navigationManager?.stop()
}
Навигатор может работать в трёх режимах: свободная навигация, ведение по маршруту и симуляция ведения.
Настройки навигатора можно изменить через свойства NavigationManager.
Свободная навигация
В этом режиме маршрут следования отсутствует, но навигатор будет информировать о превышениях скорости, дорожных камерах, авариях и ремонтных работах.
Чтобы запустить навигатор в этом режиме, нужно вызвать метод start()
без параметров.
navigationManager.start()
Ведение по маршруту
В этом режиме на карте будет построен маршрут от текущего местоположения до указанной точки назначения, и пользователь будет получать инструкции по мере движения.
Чтобы запустить навигатор в этом режиме, нужно вызвать метод start()
и указать объект RouteBuildOptions - координаты точки назначения и настройки маршрута.
let routeBuildOptions = RouteBuildOptions(
finishPoint: RouteSearchPoint(
coordinates: GeoPoint(
latitude: 55.752425,
longitude: 37.613983
)
),
routeSearchOptions: routeSearchOptions
)
navigationManager.start(routeBuildOptions)
Дополнительно при вызове метода start()
можно указать объект TrafficRoute - готовый маршрут для навигации. В таком случае навигатор не будет пытаться построить маршрут от текущего местоположения, а начнёт ведение по указанному маршруту.
// Ищем маршрут.
self.routeSearchCancellable = routesFuture.sink { routes in
guard let route = routes.first else { return }
// Настройки маршрута.
let routeBuildOptions = RouteBuildOptions(
finishPoint: finishPoint,
routeSearchOptions: routeSearchOptions
)
// Запускаем навигатор.
navigationManager.start(
routeBuildOptions: routeBuildOptions,
trafficRoute: route
)
} failure: { error in
print("Не удалось найти маршрут: \\(error)")
}
Симуляция ведения по маршруту
В этом режиме навигатор не будет отслеживать реальное местоположение устройства, а запустит симулированное движение по указанному маршруту. Режим удобно использовать для отладки.
Чтобы запустить навигатор в режиме симуляции, нужно вызвать метод startSimulation()
, указав готовый маршрут (TrafficRoute) и его настройки (RouteBuildOptions).
Скорость движения можно изменить с помощью свойства SimulationSettings.speed (метры в секунду).
navigationManager.simulationSettings.speed = 30 / 3.6
navigationManager.startSimulation(
routeBuildOptions: routeBuildOptions,
trafficRoute: route
)
Остановить симуляцию можно с помощью метода stop()
.
navigationManager.stop()
Получение объектов по экранным координатам
Информацию об объектах на карте можно получить, используя пиксельные координаты. Для этого нужно вызвать метод карты getRenderedObjects(), указав координаты в пикселях и радиус в экранных миллиметрах. Метод вернет отложенный результат, содержащий информацию обо всех найденных объектах в указанном радиусе на видимой области карты (массив RenderedObjectInfo).
Пример функции, которая принимает координаты нажатия на экран и передает их в метод getRenderedObjects()
:
private func tap(point: ScreenPoint, tapRadius: ScreenDistance) {
let cancel = map.getRenderedObjects(centerPoint: point, radius: tapRadius).sink(
receiveValue: {
infos in
// Первый объект в массиве - самый близкий к координатам.
guard let info = infos.first else { return }
// Обработка результата в главной очереди.
DispatchQueue.main.async {
[weak self] in
self?.handle(selectedObject: info)
}
},
failure: { error in
print("Ошибка получения информации об объектах: \(error)")
}
)
// Сохраняем результат вызова, так как его удаление отменяет обработку.
self.getRenderedObjectsCancellable = cancel
}
Стили карты
Для работы со стилями нужно создать объект IStyleFactory с помощью метода makeStyleFactory().
let styleFactory = sdk.makeStyleFactory()
Чтобы создать стиль карты, совместимый с SDK, воспользуйтесь функцией «Экспорт» в редакторе стилей и добавьте скачанный файл в ваш проект.
Создание карты с пользовательским стилем
Чтобы создать карту с произвольным стилем, нужно загрузить нужный стиль с помощью метода loadResource() или loadFile() фабрики стилей и указать получившийся объект в качестве параметра styleFuture
в настройках карты.
// Создаём фабрику стилей.
let styleFactory = sdk.makeStyleFactory()
// Устанавливаем начальный стиль карты в настройках.
var mapOptions = MapOptions.default
mapOptions.styleFuture = styleFactory.loadResource(name: "custom_style_file.2gis", bundle: .main)
// Создаём карту с указанными настройками.
let mapFactory = sdk.makeMapFactory(options: mapOptions)
Методы loadResource() и loadFile() возвращают отложенное значение (Future), чтобы не задерживать загрузку карты. Если стиль уже был загружен (см. следующий раздел), его можно превратить в объект Future с помощью метода makeReadyValue().
var mapOptions = MapOptions.default
mapOptions.styleFuture = Future.makeReadyValue(style)
Изменение стиля карты
Изменить стиль существующей карты можно при помощи метода setStyle().
В отличие от указания стиля при создании карты, setStyle() принимает не Future, а загруженный стиль карты (Style). Поэтому setStyle() следует вызывать после завершения загрузки Future.
// Создаём фабрику стилей.
let styleFactory = sdk.makeStyleFactory()
// Загружаем новый стиль карты. Метод loadFile() принимает только локальные URL (file://).
self.cancellable = styleFactory.loadFile(url: styleFileURL).sink(
receiveValue: { [map = self.map] style in
// Меняем стиль карты после загрузки.
map.setStyle(style: style)
},
failure: { error in
print("Не удалось загрузить стиль из файла <\(fileURL)>. Ошибка: \(error)")
})
Светлые и тёмные темы
Стили карты могут содержать несколько тем (например, дневную и ночную), между которыми можно переключаться без необходимости загрузки дополнительного стиля. Название используемой темы можно указать при создании карты с помощью параметра appearance.
В iOS 13.0 и выше можно использовать автоматическое переключение между светлой и тёмной темами (см. Dark Mode).
// Настройки карты.
var mapOptions = MapOptions.default
// Название светлой темы в используемом стиле.
let lightTheme: Theme = "day"
// Название тёмной темы в используемом стиле.
let darkTheme: Theme = "night"
if #available(iOS 13.0, *) {
// Автоматически переключаемся между темами в iOS 13.0.
mapOptions.appearance = .automatic(light: lightTheme, dark: darkTheme)
} else {
// Используем светлую тему в остальных случаях.
mapOptions.appearance = .universal(lightTheme)
}
// Создаём карту с указанными настройками.
let mapFactory = sdk.makeMapFactory(options: mapOptions)
Изменить тему после создания карты можно с помощью свойства appearance слоя карты:
// Слой карты.
let mapView = mapFactory.mapView
// Меняем тему на тёмную.
mapView.appearance = .universal(darkTheme)
Распознаватель жестов карты
Для кастомизации распознавателя жестов карты, необходимо задать реализацию протокола IMapGestureView в IMapView или реализацию IMapGestureViewFactory в MapOptions. Если ни одна из этих имплементаций задана не будет, то будет использована реализация по умолчанию. Пример такой кастомизации распознавателя можно посмотреть здесь.