Swift
Mapricot's API is plain HTTPS and JSON, so Swift's URLSession and Codable cover it, with no package to add. This page calls each API: geocoding, places, routing, and IP geolocation, and shows a map on the free tiles with MapLibre Native. The API code was tested with Swift 6.1, and works on iOS 15, macOS 12, and later.
Set up
Create a server key in the dashboard with only the products the app uses: Places for geocoding and places, Routing, or IP geolocation. Give it caps on requests per minute and per 30 days, as Getting started shows. An app sends no web origin, so a browser key is refused there with 403, and anyone who unpacks the app can read the key inside it. To keep the key out of the app entirely, send requests through your own backend instead. The map tiles need no key.
Add the key to your target's Info tab as MapricotKey. Then add the types for the fields you use, and a function that sends the key with every request, retries when the API is busy, and throws on errors. It sends a POST when there's a body:
import Foundation
struct Place: Decodable {
let id: String
let name: String
let label: String
let lat: Double
let lon: Double
let distanceMeters: Int?
}
struct Places: Decodable { let results: [Place] }
struct SavedPlace: Decodable { let result: Place }
struct Waypoint: Encodable { let lat: Double; let lon: Double }
struct RouteRequest: Encodable { let waypoints: [Waypoint]; let mode: String }
struct Route: Decodable {
let distanceMeters: Int
let durationSeconds: Int
let polyline: String
}
struct Routes: Decodable { let routes: [Route] }
struct IPLocation: Decodable {
let city: String?
let countryCode: String?
}
struct MapricotError: Error, Decodable {
struct Detail: Decodable { let message: String }
let error: Detail
}
let key = Bundle.main.object(forInfoDictionaryKey: "MapricotKey") as! String
func mapricot<T: Decodable>(_ path: String, query: [String: String] = [:], body: (any Encodable)? = nil) async throws -> T {
var components = URLComponents(string: "https://api.mapricot.com\(path)")!
if !query.isEmpty {
components.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) }
}
var request = URLRequest(url: components.url!, timeoutInterval: 30)
request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
if let body {
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(body)
}
var attempt = 1
while true {
let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as! HTTPURLResponse).statusCode
if (status == 429 || status == 503) && attempt < 4 {
try await Task.sleep(nanoseconds: UInt64(1 << attempt) * 1_000_000_000)
attempt += 1
continue
}
if status >= 400 {
throw try JSONDecoder().decode(MapricotError.self, from: data)
}
return try JSONDecoder().decode(T.self, from: data)
}
}
The examples below run in an async context, such as a SwiftUI .task { } modifier.
Geocode an address
/v1/search turns an address or place name into coordinates:
let search: Places = try await mapricot("/v1/search", query: ["q": "128 SW 3rd Ave, Portland", "limit": "1"])
for result in search.results {
print(result.label, result.lat, result.lon)
}
128 SW 3rd Avenue, Portland, OR, USA 45.521736 -122.673276
Each result also has its address in parts under address, and a type, such as address, street, or locality, that says how exact the match is. Add them to Place to read them. To limit results to some countries, add "country": "US,CA". Geocoding lists every parameter and field.
Find the address at a point
/v1/reverse turns coordinates into the nearest address:
let reverse: Places = try await mapricot("/v1/reverse", query: ["lat": "45.5217", "lon": "-122.6733", "limit": "1"])
print(reverse.results[0].label, reverse.results[0].distanceMeters ?? 0)
128 SW 3rd Avenue, Portland, OR, USA 4
Suggest places as someone types
/v1/autocomplete takes the text typed so far. focus ranks results near a point first, such as the user's location:
let suggestions: Places = try await mapricot("/v1/autocomplete", query: ["q": "stumpt", "focus": "45.52,-122.68", "limit": "1"])
let suggestion = suggestions.results[0]
print(suggestion.label, suggestion.id)
Stumptown Coffee Roasters, Portland, OR, USA mp_b3BlbnN0cmVldG1hcDp2ZW51ZTpub2RlLzExOTg2MjI2NjYw
Find places nearby
/v1/nearby lists places of a category around a point, nearest first:
let nearby: Places = try await mapricot("/v1/nearby", query: [
"lat": "45.52", "lon": "-122.68", "category": "food:coffee_shop", "radius": "1000", "limit": "1",
])
for place in nearby.results {
print(place.name, place.distanceMeters ?? 0)
}
Public Domain 37
Look up a place by ID
Every result has an id. Save it, and /v1/places/{id} fetches that place again later:
let saved: SavedPlace = try await mapricot("/v1/places/\(suggestion.id)")
print(saved.result.label)
Stumptown Coffee Roasters, Portland, OR, USA
Get a route
/v1/route takes a JSON body with two to 20 waypoints and a mode of drive, walk, or bicycle:
let routes: Routes = try await mapricot("/v1/route", body: RouteRequest(
waypoints: [Waypoint(lat: 43.7396, lon: 7.4275), Waypoint(lat: 43.734, lon: 7.4222)],
mode: "walk"
))
print(routes.routes[0].distanceMeters, routes.routes[0].durationSeconds)
1044 739
Distances are in meters and times in seconds. When no path connects the waypoints, routes is empty. The route's line is in polyline, encoded with six decimal places, so decode it with precision 6, as decoding a route polyline explains.
Look up an IP address
/v1/ip/{ip} gives the approximate location of an IPv4 or IPv6 address:
let location: IPLocation = try await mapricot("/v1/ip/8.8.8.8")
print(location.city ?? "", location.countryCode ?? "")
Mountain View US
Show a map
Map tiles are free and need no key. MapLibre Native for iOS draws them. In Xcode, choose File, then Add Package Dependencies, and add https://github.com/maplibre/maplibre-gl-native-distribution. Then wrap its MLNMapView for SwiftUI, with a Mapricot style:
import MapLibre
import SwiftUI
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MLNMapView {
let mapView = MLNMapView(frame: .zero, styleURL: URL(string: "https://tiles.mapricot.com/styles/light.json"))
mapView.setCenter(CLLocationCoordinate2D(latitude: 45.52, longitude: -122.68), zoomLevel: 12, animated: false)
return mapView
}
func updateUIView(_ mapView: MLNMapView, context: Context) {}
}
Use MapView() in a SwiftUI view like any other. The styles are light, dark, and grayscale, and they credit OpenStreetMap in the map's corner, which its license requires you to keep. MapLibre's iOS docs show how to add markers and lines.
Errors and retries
mapricot throws a MapricotError for any error response, with the API's message:
do {
let _: Places = try await mapricot("/v1/search", query: ["q": ""])
} catch let error as MapricotError {
print(error.error.message)
}
The service rejected the request.
It retries 429 and 503 responses up to three times, waiting 2, 4, then 8 seconds. Errors aren't billed, so retrying them costs nothing. A 429 that keeps coming back can also mean the key's caps or the account's credit are used up. In a search box, wait until the user pauses typing before sending a request, so each keystroke isn't one. The API reference lists every status.