Skip to main content

Range

Last updated: June 10, 2026 | 5 minutes read

This example demonstrates how to use GEMKit in a UIKit application to display multiple maps simultaneously.

Check the full implementation on GitHub.

Range View with Settings
Calculated Ranges on Map

Map Display and Range View Preparation​

The following code outlines the main view, which displays the map and prepares the range calculation view:

ViewController.swiftView on GitHub
class ViewController: UIViewController, MapViewControllerDelegate {

var mapViewController: MapViewController?
var rangeViewController: RangeViewController?

var buttonExit: UIButton?

deinit {

if let controller = mapViewController {

controller.destroy()
}
}

override func viewDidLoad() {

super.viewDidLoad()

if let navigationController = self.navigationController {

let appearance = navigationController.navigationBar.standardAppearance

navigationController.navigationBar.scrollEdgeAppearance = appearance
}

self.title = "Range Demo"

self.createMapView()
}

override func viewWillAppear(_ animated: Bool) {

super.viewWillAppear(animated)

self.mapViewController!.startRender()
}

override func viewDidAppear(_ animated: Bool) {

super.viewDidAppear(animated)

let location = CoordinatesObject.coordinates(withLatitude: 52.517477, longitude: 13.397152) // Berlin

self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 0)
}

override func viewWillDisappear(_ animated: Bool) {

super.viewWillDisappear(animated)

self.mapViewController!.stopRender()
}

// MARK: - Map View

func createMapView() {

self.mapViewController = MapViewController.init()
self.mapViewController!.delegate = self
self.mapViewController!.view.backgroundColor = UIColor.systemBackground

self.addChild(self.mapViewController!)
self.view.addSubview(self.mapViewController!.view)
self.mapViewController!.didMove(toParent: self)

self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0),
self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0),
self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0)
])
}

// MARK: - TableView

func addRangeView() {

let rangeViewController = RangeViewController.init(mapViewController: self.mapViewController!)
rangeViewController.view.layer.shadowColor = UIColor.darkGray.cgColor
rangeViewController.view.layer.shadowOpacity = 0.8

self.view.addSubview(rangeViewController.view)

rangeViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
rangeViewController.view!.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
rangeViewController.view!.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
rangeViewController.view!.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
rangeViewController.view!.heightAnchor.constraint(equalToConstant: 360)
])

let size: CGFloat = 50

let buttonExit = UIButton.init(type: .system)
buttonExit.setImage(
UIImage.init(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium)),
for: .normal)
buttonExit.addTarget(self, action: #selector(closeRangeView), for: .touchUpInside)
buttonExit.layer.shadowColor = UIColor.darkGray.cgColor
buttonExit.layer.shadowOpacity = 0.8
buttonExit.backgroundColor = UIColor.systemBackground
buttonExit.layer.cornerRadius = size / 2

self.buttonExit = buttonExit

self.view.addSubview(buttonExit)

buttonExit.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
buttonExit.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -5),
buttonExit.bottomAnchor.constraint(equalTo: rangeViewController.view!.topAnchor, constant: size - 30),
buttonExit.widthAnchor.constraint(equalToConstant: size),
buttonExit.heightAnchor.constraint(equalToConstant: size)
])

self.rangeViewController = rangeViewController
}

Range View Actions​

The following code outlines the main functions to handle the range view actions, such as closing the view and handling the range operations, including adding, deleting, and selecting range routes:

info

For this example the Range UI that shows the different route settings and range actions is done in a SwiftUI View for the sake of simplicity. You can find the full implementation of the RangeView in the GitHub repository linked above.

RangeViewController.swiftView on GitHub
func prepareView() {

guard let mapViewController = self.mapViewController else { return }

self.rangeView = RangeView(model: self.model)

self.rangeView!.didSelectOptionItem = { [weak self] item in

guard let strongSelf = self else { return }

guard let rangeView = strongSelf.rangeView else { return }

let alert = strongSelf.presentOptionsMenu(
title: item.title,
options: item.options,
selectedIndex: item.chosenOption
) { newIndex in

rangeView.updateSettingsOnOption(item: item, newOption: newIndex)
}

if let alert = alert {

strongSelf.present(alert, animated: true, completion: nil)
}
}

self.rangeView!.didSelectTransportMode = { [weak self] in

guard let strongSelf = self else { return }
guard let rangeView = strongSelf.rangeView else { return }

let alert = strongSelf.presentOptionsMenu(
title: "Transport Mode",
options: [
"Car",
"Pedestrian",
"Bicycle",
"Truck"
],
selectedIndex: strongSelf.model.mode.rawValue
) { newIndex in

if strongSelf.model.mode.rawValue != newIndex {

rangeView.updateTransportModeOnOption(newOption: newIndex)
}
}

if let alert = alert {

strongSelf.present(alert, animated: true, completion: nil)
}
}

self.rangeView!.onRangeAdded = { [weak self] in

guard let strongSelf = self else { return }

if strongSelf.model.isRangeValueUsed(value: strongSelf.model.rangeValue, transportMode: strongSelf.model.mode) == false {

strongSelf.calculateRangeRoutes()
}
}

self.rangeView!.onRangeDeleted = { [weak self] item in

guard let strongSelf = self else { return }

mapViewController.removeRoutes(item.routes)

strongSelf.colors[item.routeColor] = false

strongSelf.centerOnRoutes(routes: strongSelf.model.getSelectedRangeRoutes())
}

self.rangeView!.onRangeSelected = { [weak self] item in

guard self != nil else { return }

if item.isSelected {

// let insets = strongSelf.calculateAreaInset(margin: 10)
// mapViewController.setEdgeAreaInsets(insets)

mapViewController.presentRoutes(item.routes, withTraffic: nil, showSummary: false, animationDuration: 200)

// mapViewController.setDebugEdgeAreaVisible(true)

if let route = item.routes.first {

let renderSettings = MapViewRouteRenderSettings.init()
renderSettings.options = Int32(MapViewRouteRenderOption.main.rawValue)
renderSettings.fillColor = item.routeColor

renderSettings.contourInnerSize = 0.3
renderSettings.contourOuterSize = 0.3

let preferences = mapViewController.getPreferences()
preferences.setRenderSettings(renderSettings, route: route)
}

} else {

mapViewController.removeRoutes(item.routes)
}
}

let controller = UIHostingController.init(rootView: self.rangeView!)

self.addChild(controller)
self.view.addSubview(controller.view)
controller.didMove(toParent: self)

controller.view.translatesAutoresizingMaskIntoConstraints = false

let array: [NSLayoutConstraint] = [
controller.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
controller.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0),
controller.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0),
controller.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0)
]
NSLayoutConstraint.activate(array)

controller.view.backgroundColor = .systemBackground
}

Range Calculation​

The following code demonstrates how to calculate the range based on the selected settings in the range view and present the results on the map:

RangeViewController.swiftView on GitHub
// MARK: - Draw Routes

func calculateRangeRoutes() {

guard let mapViewController = self.mapViewController else { return }

guard let rangeLandmark = landmark else { return }

let value = NSNumber.init(value: model.rangeType == .time ? Int(model.rangeValue) * 60 : Int(model.rangeValue))

let routePreferences = self.getRoutePreferences(transportMode: model.mode)

//Range value setting for the route calculation.
routePreferences.setRouteRanges([value], quality: 100)

self.navigationContext = NavigationContext.init(preferences: routePreferences)

self.model.isCalculating = true

self.navigationContext.calculateRoute(
withWaypoints: [rangeLandmark],
completionHandler: { [weak self] (results: [RouteObject]) in

guard let strongSelf = self else { return }

strongSelf.model.isCalculating = false

if !results.isEmpty, let route = results.first {

let rangeRouteItem = RangeValueRoutesItem(
rangeType: strongSelf.model.rangeType, rangeValue: strongSelf.model.rangeValue, routes: [route],
transportMode: strongSelf.model.mode)
strongSelf.model.addPresentedRoutes(rangeRouteItem)

let color: UIColor =
strongSelf.model.presentedRoutes.count > 1
? (strongSelf.colors.someKey(forValue: false) ?? strongSelf.initialColor) : strongSelf.initialColor

// let insets = strongSelf.calculateAreaInset(margin: 10)
// mapViewController.setEdgeAreaInsets(insets)

mapViewController.presentRoutes(results, withTraffic: nil, showSummary: false, animationDuration: 800)

// mapViewController.setDebugEdgeAreaVisible(true)

rangeRouteItem.routeColor = color
strongSelf.colors[color] = true

let renderSettings = MapViewRouteRenderSettings.init()
renderSettings.options = Int32(MapViewRouteRenderOption.main.rawValue)
renderSettings.fillColor = color

renderSettings.contourInnerSize = 0.3
renderSettings.contourOuterSize = 0.3

let preferences = mapViewController.getPreferences()
preferences.setRenderSettings(renderSettings, route: route)
}
})
}