Skip to main content

Debug map

This example shows how the map looks with different debug options.
DebugMapExample.swift
import UIKit
@_spi(Experimental) import MapboxMaps

private protocol DebugOptionSettingsDelegate: AnyObject {
func debugOptionSettingsDidChange(_ controller: SettingsViewController)
}

private struct MapDebugOptionSetting {
let debugOption: MapViewDebugOptions
let displayTitle: String
}

final class DebugMapExample: UIViewController, ExampleProtocol, DebugOptionSettingsDelegate {
private var mapView: MapView!

override func viewDidLoad() {
super.viewDidLoad()

mapView = MapView(frame: view.bounds)
if #available(iOS 15.0, *) {
let maxFPS = Float(UIScreen.main.maximumFramesPerSecond)
mapView.preferredFrameRateRange = CAFrameRateRange(minimum: 1, maximum: maxFPS, preferred: maxFPS)
}

view.addSubview(mapView)
view.backgroundColor = .skyBlue
mapView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mapView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])

let debugOptionsBarItem = UIBarButtonItem(
title: "Debug",
style: .plain,
target: self,
action: #selector(openDebugOptionsMenu(_:)))
let tileCover = UIBarButtonItem(
title: "Tiles",
style: .plain,
target: self,
action: #selector(tileCover))
navigationItem.rightBarButtonItems = [debugOptionsBarItem, tileCover]
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// The below line is used for internal testing purposes only.
finish()
}

@objc private func openDebugOptionsMenu(_ sender: UIBarButtonItem) {
let settingsViewController = SettingsViewController(debugOptions: mapView.debugOptions)
settingsViewController.delegate = self

let navigationController = UINavigationController(rootViewController: settingsViewController)
navigationController.modalPresentationStyle = .popover
navigationController.popoverPresentationController?.barButtonItem = sender

present(navigationController, animated: true, completion: nil)
}

@objc private func tileCover() {
let tileIds = mapView.mapboxMap.tileCover(for: TileCoverOptions(tileSize: 512, minZoom: 0, maxZoom: 22, roundZoom: false))
let message = tileIds.map { "\($0.z)/\($0.x)/\($0.y)" }.joined(separator: "\n")
showAlert(withTitle: "Displayed tiles", and: message)
}

fileprivate func debugOptionSettingsDidChange(_ controller: SettingsViewController) {
controller.dismiss(animated: true, completion: nil)
mapView.debugOptions = controller.enabledDebugOptions
}
}

private final class SettingsViewController: UIViewController, UITableViewDataSource {

weak var delegate: DebugOptionSettingsDelegate?
private var listView: UITableView!

private(set) var enabledDebugOptions: MapViewDebugOptions
private let allSettings: [MapDebugOptionSetting] = [
MapDebugOptionSetting(debugOption: .collision, displayTitle: "Debug collision"),
MapDebugOptionSetting(debugOption: .depthBuffer, displayTitle: "Show depth buffer"),
MapDebugOptionSetting(debugOption: .overdraw, displayTitle: "Debug overdraw"),
MapDebugOptionSetting(debugOption: .parseStatus, displayTitle: "Show tile coordinate"),
MapDebugOptionSetting(debugOption: .stencilClip, displayTitle: "Show stencil buffer"),
MapDebugOptionSetting(debugOption: .tileBorders, displayTitle: "Debug tile clipping"),
MapDebugOptionSetting(debugOption: .timestamps, displayTitle: "Show tile loaded time"),
MapDebugOptionSetting(debugOption: .modelBounds, displayTitle: "Show 3D model bounding boxes"),
MapDebugOptionSetting(debugOption: .light, displayTitle: "Show light conditions"),
MapDebugOptionSetting(debugOption: .camera, displayTitle: "Show camera debug view"),
MapDebugOptionSetting(debugOption: .padding, displayTitle: "Camera padding")
]

init(debugOptions: MapViewDebugOptions) {
enabledDebugOptions = debugOptions
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

title = "Debug options"
listView = UITableView()
listView.dataSource = self
listView.register(DebugOptionCell.self, forCellReuseIdentifier: String(describing: DebugOptionCell.self))

view.addSubview(listView)

listView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
listView.topAnchor.constraint(equalTo: view.topAnchor),
listView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
listView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
listView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])

navigationItem.largeTitleDisplayMode = .never
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .save,
target: self,
action: #selector(saveSettings(_:)))
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
preferredContentSize = listView.contentSize
}

@objc private func saveSettings(_ sender: UIBarButtonItem) {
delegate?.debugOptionSettingsDidChange(self)
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
allSettings.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellID = String(describing: DebugOptionCell.self)
// swiftlint:disable:next force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! DebugOptionCell

let setting = allSettings[indexPath.row]
cell.configure(with: setting, isOptionEnabled: enabledDebugOptions.contains(setting.debugOption))
cell.onToggled { [unowned self] isEnabled in
if isEnabled {
self.enabledDebugOptions.insert(setting.debugOption)
} else {
self.enabledDebugOptions.remove(setting.debugOption)
}
}

return cell
}
}

// MARK: Cell

private class DebugOptionCell: UITableViewCell {

private let titleLabel = UILabel()
private let toggle = UISwitch()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
toggle.addTarget(self, action: #selector(didToggle(_:)), for: .valueChanged)

contentView.addSubview(titleLabel)
contentView.addSubview(toggle)

titleLabel.translatesAutoresizingMaskIntoConstraints = false
toggle.translatesAutoresizingMaskIntoConstraints = false

let constraints: [NSLayoutConstraint] = [
titleLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 16),
titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
titleLabel.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8),
toggle.leftAnchor.constraint(greaterThanOrEqualTo: titleLabel.rightAnchor, constant: 16),
toggle.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -16),
toggle.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor),
toggle.topAnchor.constraint(greaterThanOrEqualTo: contentView.topAnchor, constant: 8),
]
NSLayoutConstraint.activate(constraints)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(with setting: MapDebugOptionSetting, isOptionEnabled: Bool) {
titleLabel.text = setting.displayTitle
toggle.isOn = isOptionEnabled
}

private var onToggleHandler: ((Bool) -> Void)?

func onToggled(_ handler: @escaping (Bool) -> Void) {
onToggleHandler = handler
}

@objc private func didToggle(_ sender: UISwitch) {
onToggleHandler?(sender.isOn)
}
}
Was this example helpful?