Skip to main content

Combine

This example demonstrates how to use Map events in conjunction with the Combine framework for handling map-related events in an iOS app. The code showcases the usage of Combine publishers to react to events, specifically the camera changes on the Mapbox Maps SDK for iOS. The MapView instance listens for camera changes using the onCameraChanged event signal and processes them with Combine functionalities. The camera state data retrieved from the event is then displayed in a formatted manner on a UILabel within the view.

The implementation includes setting up the MapView, subscribing to camera change events, updating the camera label with the relevant map information, and incorporating Combine's powerful capabilities seamlessly into mapping interactions. By combining MapboxMaps, Combine publishers, and UIKit components, developers can efficiently manage and respond to map events using reactive programming paradigms in their iOS applications.

iOS Examples App Available

This example code is part of the Maps SDK for iOS Examples App, a working iOS project available on Github. 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 tutorial for step-by-step instructions.

CombineExample.swift
import UIKit
import MapboxMaps
import Combine

/// This examples shows how to use Map events with Combine framework.
@available(iOS 13.0, *)
final class ViewController: UIViewController {
private var mapView: MapView!
private var tokens = Set<AnyCancellable>()
private let cameraLabel = UILabel.makeCameraLabel()

override func viewDidLoad() {
super.viewDidLoad()

mapView = MapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(mapView)

cameraLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cameraLabel)
cameraLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24).isActive = true
cameraLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

// The on-prefixed event signals can be used as publishers:
mapView.mapboxMap.onCameraChanged
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.map(\.cameraState)
.print("Camera State")
.sink { [weak self] state in
self?.cameraLabel.attributedText = .formatted(cameraSate: state)
self?.cameraLabel.setNeedsLayout()
}
.store(in: &tokens)
}


}

private extension UILabel {
static func makeCameraLabel() -> UILabel {
let label = UILabel()
label.numberOfLines = 0
if #available(iOS 13.0, *) {
label.backgroundColor = UIColor.systemBackground
} else {
label.backgroundColor = .white
}
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
return label
}
}

private extension NSAttributedString {
static func logString(_ text: String, bold: Bool = false) -> NSAttributedString {
var attributes = [NSAttributedString.Key: Any]()
if #available(iOS 13.0, *) {
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
}
}
Was this example helpful?