iOS Example

A Swift/SwiftUI application that renders a MapLibre Native vector tile map and adds places search. Uses UIViewRepresentable to bridge MapLibre's UIKit view into SwiftUI.

Prerequisites

Quick Start

git clone https://github.com/geogdev/examples.git
cd examples/mobile/ios-maplibre
open GeogExample.xcodeproj

Wait for Swift Package Manager to resolve dependencies, then add your API key to Config.xcconfig:

GEOG_API_KEY = gk_live_your-api-key-here

Press Cmd+R to build and run.

Project Structure

FileDescription
Config.xcconfigBuild configuration — API key injected via build settings into Info.plist
GeogExample/Services/GeogService.swiftAPI client — token exchange, caching at 80% TTL, and places search
GeogExample/MapView.swiftUIViewRepresentable bridge — wraps MLNMapView with programmatic style
GeogExample/PlacesSearchView.swiftSwiftUI search panel — search input, results list, and map marker management
GeogExample/Models/Place.swiftCodable data model for search results
GeogExample/ContentView.swiftRoot SwiftUI view composing map and search

How It Works

API Key Configuration

The API key is stored in Config.xcconfig and injected into the app through Xcode build settings → Info.plist. GeogService reads it from the bundle at launch:

let apiKey = Bundle.main.object(forInfoDictionaryKey: "GEOG_API_KEY") as! String

Token Exchange

GeogService exchanges the long-lived API key for a short-lived access token and caches it:

⚠️ Production note: This example embeds the API key in the app bundle for simplicity. In a production app distributed to end users, the API key can be extracted from the binary. Use a backend token-proxy instead — your server holds the long-lived key and returns short-lived tokens to the app. See Token Exchange API for details.

func getAccessToken() async throws -> String {
    let now = Date()
    if let cached = cachedToken, now < tokenExpiresAt {
        return cached
    }

    var request = URLRequest(url: URL(string: "https://api.geog.dev/v1/auth/token")!)
    request.httpMethod = "POST"
    request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try JSONSerialization.data(withJSONObject: ["ttl": 3600, "scope": "tiles:read places:read"])

    let (data, _) = try await URLSession.shared.data(for: request)
    let response = try JSONDecoder().decode(TokenResponse.self, from: data)

    cachedToken = response.accessToken
    tokenExpiresAt = now.addingTimeInterval(Double(response.expiresIn) * 0.8)
    return response.accessToken
}

Map Authentication

After obtaining an access token, the app configures MLNNetworkConfiguration so all tile requests include the Bearer header automatically:

let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
    "Authorization": "Bearer \(accessToken)"
]
MLNNetworkConfiguration.shared.sessionConfiguration = config

UIViewRepresentable Bridge

MapView wraps MLNMapView (UIKit) for use in SwiftUI using the UIViewRepresentable protocol with a Coordinator for delegate callbacks.

Full Source

github.com/geogdev/examples/tree/main/mobile/ios-maplibre

See Also