iOS SDK Примеры (unstable) | 2GIS Documentation
iOS SDK

Для работы с 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
)

Сначала нужно обратиться в техническую поддержку 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. Если ни одна из этих имплементаций задана не будет, то будет использована реализация по умолчанию. Пример такой кастомизации распознавателя можно посмотреть здесь.