> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/ios/ja/maps/llms.txt)

# Map events

![Print out map events and data.](https://docs.mapbox.com/ios/ja/assets/ideal-img/maps-examples-map-events.2d8520d.480.png)

This example demonstrates how to handle map events using the **Mapbox Maps SDK for iOS**. The `MapEventsExample` class is a `UIViewController` that includes a [`MapView`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/map-view/), a `UITableView` for logging events, a camera label, and a clear button.

The example showcases the usage of various event types such as [`onMapLoaded`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/mapboxmap/onmaploaded/), [`onMapLoadingError`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/mapboxmap/onmaploadingerror/), [`onStyleDataLoaded`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/mapboxmap/onstyleloaded/), [`onSourceAdded`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/mapboxmap/onsourceadded/), and more. The UI layout is dynamically adjusted, and camera state information is displayed in the camera label.

> **Note: iOS Examples App Available**
> 
> This example code is part of the **Maps SDK for iOS Examples App**, a working iOS project [available on Github](https://github.com/mapbox/mapbox-maps-ios/tree/11.31.0/Sources/Examples). iOS developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.
> 
> See our [Run the Maps SDK for iOS Examples App](https://docs.mapbox.com/help/tutorials/maps-sdk-ios-examples-app/) tutorial for step-by-step instructions.

**Swift**

Title: `MapEventsExample.swift`

[View on GitHub](https://github.com/mapbox/mapbox-maps-ios/blob/main/Sources/Examples/All%20Examples/Lab/MapEventsExample.swift)

```swift
import UIKit
import MapboxMaps

final class ViewController: UIViewController {
    private var mapView: MapView!
    private let tableView = UITableView()
    private let cameraLabel = UILabel.makeCameraLabel()
    private var clearButton = UIButton(type: .system)
    private var cancelables = Set<AnyCancelable>()
    var entries = [NSAttributedString]() {
        didSet {
            tableView.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground

        mapView = MapView(frame: view.bounds)
        mapView.ornaments.options.scaleBar.visibility = .visible
        view.addSubview(mapView)

        tableView.dataSource = self
        tableView.register(LogCell.self, forCellReuseIdentifier: "cell")
        tableView.allowsSelection = false
        tableView.separatorStyle = .none
        view.addSubview(tableView)

        clearButton.setTitle("Clear", for: .normal)
        clearButton.addTarget(self, action: #selector(clear), for: .touchUpInside)
        view.addSubview(clearButton)

        view.addSubview(cameraLabel)

        let map = mapView.mapboxMap!
        logEvent(map.onMapLoaded)
        logEvent(map.onMapLoadingError)
        logEvent(map.onStyleLoaded)
        logEvent(map.onStyleDataLoaded)
        logEvent(map.onMapIdle)
        logEvent(map.onSourceAdded)
        logEvent(map.onSourceRemoved)
        logEvent(map.onSourceDataLoaded)
        logEvent(map.onStyleImageMissing)
        logEvent(map.onStyleImageRemoveUnused)
        // onResourceRequest produces too much logs for demonstration, uncomment it if needed.
        // logEvent(mapView.mapboxMap.onResourceRequest)
        logEvent(map.onStyleAttributionsChanged)

        map.onCameraChanged.observe { [weak self] event in
            self?.cameraLabel.attributedText = .formatted(cameraSate: event.cameraState)
            self?.view.setNeedsLayout()
        }.store(in: &cancelables)
    }

    @objc private func clear() {
        entries.removeAll()
    }

    func logEvent<T: LogableEvent>(_ signal: Signal<T>) {
        signal.observe { [weak self] event in
            self?.entries.append(.formatted(event: event))
            print("MapEvent: \(event.logString)")
        }.store(in: &cancelables)
    }

    

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        let bounds = view.bounds.inset(by: UIEdgeInsets(
            top: view.safeAreaInsets.top, left: 0, bottom: 0, right: 0))
        let halfHeight = bounds.height / 2 + 100

        mapView.frame = CGRect(x: 0, y: bounds.minY, width: bounds.width, height: halfHeight)

        tableView.frame = CGRect(x: 0, y: bounds.minY + halfHeight, width: bounds.width, height: bounds.height - halfHeight)

        let buttonSize = clearButton.sizeThatFits(bounds.size)
        clearButton.frame = CGRect(
            origin: CGPoint(
                x: bounds.width - buttonSize.width - 10,
                y: bounds.minY + halfHeight + 10),
            size: buttonSize)

        let labelSize = cameraLabel.sizeThatFits(bounds.size)
        cameraLabel.frame = CGRect(
            origin: CGPoint(
                x: (bounds.width - labelSize.width) / 2,
                y: bounds.minY + halfHeight - labelSize.height - 10),
            size: labelSize)
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { entries.count }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell =  tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! LogCell
        cell.logLabel.attributedText = entries[entries.count - 1 - indexPath.row]
        return cell
    }
}

private class LogCell: UITableViewCell {
    let logLabel = UILabel()
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        logLabel.numberOfLines = 0
        logLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(logLabel)
        NSLayoutConstraint.activate([
            logLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 6),
            logLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -6),
            logLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
            logLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3),
        ])
    }

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

protocol LogableEvent {
    var name: String { get }
    var info: String { get }
}

extension LogableEvent {
    var logString: String {
        return "[\(name)] \(info)"
    }
}

private extension NSAttributedString {
    static func logString(_ text: String, bold: Bool = false) -> NSAttributedString {
        var attributes = [NSAttributedString.Key: Any]()
        attributes[.font] = UIFont.monospacedSystemFont(ofSize: 13, weight: bold ? .bold : .regular)
        return NSAttributedString(string: text, attributes: attributes)
    }

    static func formatted(cameraSate: CameraState) -> NSAttributedString {
        let str = NSMutableAttributedString()
        str.append(.logString("lat:", bold: true))
        str.append(.logString(" \(String(format: "%.2f", cameraSate.center.latitude))\n"))
        str.append(.logString("lon:", bold: true))
        str.append(.logString(" \(String(format: "%.2f", cameraSate.center.longitude))\n"))
        str.append(.logString("zoom:", bold: true))
        str.append(.logString(" \(String(format: "%.2f", cameraSate.zoom))"))
        if cameraSate.bearing != 0 {
            str.append(.logString("\nbearing:", bold: true))
            str.append(.logString(" \(String(format: "%.2f", cameraSate.bearing))"))
        }
        if cameraSate.pitch != 0 {
            str.append(.logString("\npitch:", bold: true))
            str.append(.logString(" \(String(format: "%.2f", cameraSate.pitch))"))
        }
        return str
    }

    static func formatted(event: LogableEvent) -> NSAttributedString {
        let str = NSMutableAttributedString()
        str.append(.logString(event.name, bold: true))
        if !event.info.isEmpty {
            let withNewLines = event.info.replacingOccurrences(of: ", ", with: "\n")
            str.append(.logString("\n\(withNewLines)"))
        }
        return str
    }
}

extension MapLoaded: LogableEvent {
    var name: String { "MapLoaded" }
    var info: String { "ti: \(timeInterval.log)" }
}

extension MapIdle: LogableEvent {
    var name: String { "MapIdle" }
    var info: String { "ts: \(timestamp)" }
}

extension MapLoadingError: LogableEvent {
    var name: String { "MapLoadingError" }
    var info: String { "ts: \(timestamp), type: \(type), message: \(message), sourceId: \(String(describing: sourceId)), tileId: \(tileId?.log ?? "nil")" }
}

extension StyleLoaded: LogableEvent {
    var name: String { "StyleLoaded" }
    var info: String { "ti: \(timeInterval.log)" }
}

extension StyleDataLoaded: LogableEvent {
    var name: String { "StyleDataLoaded" }
    var info: String { "ti: \(timeInterval.log), type: \(type)" }
}

extension SourceAdded: LogableEvent {
    var name: String { "SourceAdded" }
    var info: String { "ts: \(timestamp), sourceId: \(sourceId)" }
}

extension SourceRemoved: LogableEvent {
    var name: String { "SourceRemoved" }
    var info: String { "ts: \(timestamp), sourceId: \(sourceId)" }
}

extension SourceDataLoaded: LogableEvent {
    var name: String { "SourceDataLoaded" }
    var info: String { "ti: \(timeInterval.log), sourceId: \(sourceId), tileId: \(tileId?.log ?? "nil"), dataID: \(dataId ?? "nil"), loaded: \(loaded.log)" }
}

extension StyleImageMissing: LogableEvent {
    var name: String { "StyleImageMissing" }
    var info: String { "ts: \(timestamp), imageId: \(imageId)" }
}

extension StyleImageRemoveUnused: LogableEvent {
    var name: String { "StyleImageRemoveUnused" }
    var info: String { "ts: \(timestamp), imageId: \(imageId)" }
}

extension ResourceRequest: LogableEvent {
    var name: String { "ResourceRequest" }
    var info: String { "ti: \(timeInterval), source: \(source), url: \(request.url)" }
}

extension StyleAttributionsChanged: LogableEvent {
    var name: String { "StyleAttributionsChanged" }
    var info: String { "ts: \(timestamp), attributions: \(attributions)" }
}

extension Optional where Wrapped: CustomStringConvertible {
    var log: String {
        switch self {
        case .none: return "nil"
        case let .some(val): return "\(val)"
        }
    }
}

extension EventTimeInterval {
    var log: String {
        "\(begin) - \(end)"
    }
}

extension CanonicalTileID {
    var log: String {
        "\(z)/\(x)/\(y)"
    }
}

extension CustomRasterSourceTileStatus {
    var log: String {
        switch self {
        case .required: return "required"
        case .optional: return "optional"
        case .notNeeded: return "notNeeded"
        default: return "unknown"
        }
    }
}

extension StyleDataLoadedType: CustomDebugStringConvertible {
    public var debugDescription: String {
        switch self {
        case .style: return "style"
        case .sources: return "sources"
        case .sprite: return "sprite"
        default: return "unknown"
        }
    }
}

extension SourceDataLoadedType: CustomDebugStringConvertible {
    public var debugDescription: String {
        switch self {
        case .metadata: return "metadata"
        case .tile: return "tile"
        default: return "unknown"
        }
    }
}

extension RequestDataSourceType: CustomDebugStringConvertible {
    public var debugDescription: String {
        switch self {
        case .asset: return "asset"
        case .database: return "database"
        case .fileSystem: return "fileSystem"
        case .network: return "network"
        case .resourceLoader: return "resourceLoader"
        default: return "unknown"
        }
    }
}

private extension UILabel {
    static func makeCameraLabel() -> UILabel {
        let label = UILabel()
        label.numberOfLines = 0
        label.backgroundColor = UIColor.systemBackground
        label.layer.cornerRadius = 5
        label.layer.masksToBounds = true
        return label
    }
}
```