Skip to main content

Projections

Last updated: June 10, 2026 | 7 minutes read

This example demonstrates how to use GEMKit in a UIKit application to convert the coordinates of a selected landmark to different projections and display the results in a table view.

Check the full implementation on GitHub.

Projections Table

Map Display and Projections Table View​

The following code outlines the main view, which displays the map and prepares the table view to show the different projections for a selected landmark:

ViewController.swiftView on GitHub
class ItemProjection: NSObject {

var title: NSAttributedString = NSAttributedString.init(string: "")
var details: NSAttributedString = NSAttributedString.init(string: "")
var object: ProjectionObject?
}

class ViewController: UIViewController, MapViewControllerDelegate, UITableViewDataSource, UITableViewDelegate {

var mapViewController: MapViewController?
var projectionContext: ProjectionContext?

var tableView: UITableView?
var buttonExit: UIButton?
var modelData: [ItemProjection] = []
var selectedLandmark: LandmarkObject?

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 = "Projections"

self.projectionContext = ProjectionContext.init()

self.createMapView()

self.addTableView()
}

override func viewWillAppear(_ animated: Bool) {

super.viewWillAppear(animated)

self.mapViewController!.startRender()
}

override func viewDidAppear(_ animated: Bool) {

super.viewDidAppear(animated)

let location = CoordinatesObject.coordinates(withLatitude: 53.592590, longitude: 9.924337) // Hamburg

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 addTableView() {

self.tableView = UITableView.init(frame: self.view.frame, style: .insetGrouped)
self.tableView!.isHidden = true
self.tableView!.dataSource = self
self.tableView!.delegate = self

self.view.addSubview(self.tableView!)

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

let size: CGFloat = 50

let buttonExit = UIButton.init(type: .system)
buttonExit.isHidden = true
buttonExit.setImage(
UIImage.init(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium)),
for: .normal)
buttonExit.addTarget(self, action: #selector(closeTableView), 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: self.tableView!.topAnchor, constant: size + 5),
buttonExit.widthAnchor.constraint(equalToConstant: size),
buttonExit.heightAnchor.constraint(equalToConstant: size)
])
}

// MARK: - MapViewControllerDelegate

func mapViewController(_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onTouch point: CGPoint) {

guard let landmark = landmarks.first else { return }

self.handleSelection(landmark: landmark)
}

func mapViewController(
_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onLongTouch point: CGPoint
) {

guard let landmark = landmarks.first else { return }

self.handleSelection(landmark: landmark)
}

func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onTouch point: CGPoint) {

guard let landmark = streets.first else { return }

self.handleSelection(landmark: landmark)
}

func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onLongTouch point: CGPoint) {

guard let landmark = streets.first else { return }

self.handleSelection(landmark: landmark)
}

// MARK: - UITableViewDataSource

func numberOfSections(in tableView: UITableView) -> Int {

return 1
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

let count = self.modelData.count

return count
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {

if let lmk = self.selectedLandmark {

return lmk.getLandmarkName()
}

return ""
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let identifier = "defaultCellId"

var cell = tableView.dequeueReusableCell(withIdentifier: identifier)

if cell == nil {

cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: identifier)

cell!.textLabel!.numberOfLines = 0
cell!.detailTextLabel!.numberOfLines = 0
}

let item = self.modelData[indexPath.row]

cell!.textLabel?.attributedText = item.title
cell!.detailTextLabel?.attributedText = item.details

return cell!
}

// MARK: - UITableViewDelegate

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

return UITableView.automaticDimension
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

tableView.deselectRow(at: indexPath, animated: true)
}

Converting Coordinates to Different Projections​

The following code outlines the main functions to handle the landmark selection, highlight it on the map, and convert its coordinates to different projections:

ViewController.swiftView on GitHub
// MARK: - Utils

func handleSelection(landmark: LandmarkObject) {

self.modelData.removeAll()

let attributesMessage = self.getStringAttributes()

let coordinates = landmark.getCoordinates()

if coordinates.isValid() {

let details = NSAttributedString.init(
string: "Latitude: \(coordinates.latitude)\nLongitude: \(coordinates.longitude)", attributes: attributesMessage)

let item = ItemProjection.init()
item.title = self.getString(title: "World Geodetic System:")
item.details = details
self.modelData.append(item)
}

self.convertFrom(coordinates: coordinates, toType: .mgrs) { error, details in

let item = ItemProjection.init()
item.title = self.getString(title: "Military Grid Reference System:")
item.details = self.getString(title: "", description: "Not supported.")

if error == .kNoError {

item.details = details
}

self.modelData.append(item)
self.tableView!.reloadData()
}

self.convertFrom(coordinates: coordinates, toType: .bng) { error, details in

let item = ItemProjection.init()
item.title = self.getString(title: "British National Grid:")
item.details = self.getString(title: "", description: "Not supported.")

if error == .kNoError {

item.details = details
}

self.modelData.append(item)
self.tableView!.reloadData()
}

self.convertFrom(coordinates: coordinates, toType: .lam) { error, details in

let item = ItemProjection.init()
item.title = self.getString(title: "Lambert 93")
item.details = self.getString(title: "", description: "Not supported.")

if error == .kNoError {

item.details = details
}

self.modelData.append(item)
self.tableView!.reloadData()
}

self.convertFrom(coordinates: coordinates, toType: .utm) { error, details in

let item = ItemProjection.init()
item.title = self.getString(title: "Universal Transverse Mercator:")
item.details = self.getString(title: "", description: "Not supported.")

if error == .kNoError {

item.details = details
}

self.modelData.append(item)
self.tableView!.reloadData()
}

self.convertFrom(coordinates: coordinates, toType: .gk) { error, details in

let item = ItemProjection.init()
item.title = self.getString(title: "Gauss-Krueger")
item.details = self.getString(title: "", description: "Not supported.")

if error == .kNoError {

item.details = details
}

self.modelData.append(item)
self.tableView!.reloadData()
}

self.convertFrom(coordinates: coordinates, toType: .W3W) { error, details in

let item = ItemProjection.init()
item.title = self.getString(title: "What 3 Words")
item.details = self.getString(title: "", description: "Not supported.")

if error == .kNoError {

item.details = details
}

self.modelData.append(item)
self.tableView!.reloadData()
}

// let text = landmark.getLandmarkName()
// let scale = UIScreen.main.scale
// let image = landmark.getLandmarkImage(CGSize.init(width: 40*scale, height: 40*scale))

self.highlight(landmark: landmark)

self.selectedLandmark = landmark

self.tableView!.reloadData()
self.tableView!.isHidden = false
self.buttonExit!.isHidden = false
}

func highlight(landmark: LandmarkObject) {

let settings = HighlightRenderSettings.init()
settings.showPin = true
settings.imageSize = 7

if landmark.isContourGeograficAreaEmpty() == false {

settings.options = Int32(
HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.showContour.rawValue)
settings.contourInnerColor = UIColor.white
settings.contourOuterColor = UIColor.systemBlue
}

self.mapViewController!.presentHighlights([landmark], settings: settings, highlightId: 0)

if self.selectedLandmark == nil {

self.mapViewController!.center(onCoordinates: landmark.getCoordinates(), zoomLevel: 70, animationDuration: 400)
}
}

func convertFrom(
coordinates: CoordinatesObject, toType: ProjectionType,
completionHandler: (@escaping (_ error: SDKErrorCode, _ details: NSAttributedString) -> Void)
) {

guard let projectionContext = self.projectionContext else { return }

let fromProj = ProjectionWGS84Object.init(coordinates: coordinates)

var toProj: ProjectionObject?

switch toType {

case .bng:
toProj = ProjectionBNGObject.init()

case .lam:
toProj = ProjectionLAMObject.init()

case .utm:
toProj = ProjectionUTMObject.init()

case .mgrs:
toProj = ProjectionMGRSObject.init()

case .gk:
toProj = ProjectionGKObject.init()

case .W3W:
//
// Token is available at: https://developer.what3words.com
//
/* toProj = ProjectionW3WObject(token: "") */
break

default:
completionHandler(SDKErrorCode.kNotSupported, NSMutableAttributedString.init())
}

if let toProj = toProj {

let attributesMessage = self.getStringAttributes()

projectionContext.convert(fromProj, to: toProj) { error in

let details = NSMutableAttributedString.init()

if error == .kNoError {

switch toProj.getType() {

case .bng:
if let toProj = toProj as? ProjectionBNGObject {

let easting = toProj.getEasting()
let northing = toProj.getNorthing()
let gridRef = toProj.getGridReference()

details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nGrid: \(gridRef)", attributes: attributesMessage))
}

case .lam:
if let toProj = toProj as? ProjectionLAMObject {

let x = toProj.getX()
let y = toProj.getY()

details.append(NSMutableAttributedString.init(string: "X: \(x)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nY: \(y)", attributes: attributesMessage))
}

case .utm:
if let toProj = toProj as? ProjectionUTMObject {

let x = toProj.getX()
let y = toProj.getY()
let zone = toProj.getZone()
let hemisphere = toProj.getHemisphere() == .north ? "North" : "South"

details.append(NSMutableAttributedString.init(string: "X: \(x)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nY: \(y)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage))
details.append(
NSMutableAttributedString.init(string: "\nHemisphere: \(hemisphere)", attributes: attributesMessage))
}

case .mgrs:
if let toProj = toProj as? ProjectionMGRSObject {

let easting = toProj.getEasting()
let northing = toProj.getNorthing()
let zone = toProj.getZone()
let letters = toProj.getSq100kIdentifier()

details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage))
details.append(
NSMutableAttributedString.init(
string: "\n100,000 meter square id: \(letters)", attributes: attributesMessage))
}

case .gk:
if let toProj = toProj as? ProjectionGKObject {

let easting = toProj.getEasting()
let northing = toProj.getNorthing()
let zone = toProj.getZone()

details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage))
}

case .W3W:
if let toProj = toProj as? ProjectionW3WObject {

let words = toProj.getWords()

details.append(NSMutableAttributedString.init(string: "Words: \(words)", attributes: attributesMessage))
}

default:
break
}
}

completionHandler(error, details)
}
}
}