iOS SDK Examples & Troubleshooting
Complete implementation examples and solutions to common integration challenges.
Quickstart
The full lifecycle, stripped of UI:
import DoorstepDropoffSDK
// Once at startup
DoorstepAI.setApiKey(key: "YOUR_API_KEY_HERE")
// Per delivery, driven by your geofence
Task {
do {
try await DoorstepAI.startDeliveryByAddressString(
address: "123 Main St, Apt 4B, San Francisco, CA 94102",
deliveryId: "delivery_12345",
coordinates: LatLngObject(lat: 37.7749, lng: -122.4194)
)
try await DoorstepAI.markDropoff(deliveryId: "delivery_12345", dropoffType: .pod)
await DoorstepAI.stopDelivery(deliveryId: "delivery_12345")
} catch {
// Handle error
}
}
Full test-app view
The view below wires those calls to a UI for manual testing (SwiftUI):
Show full component
import SwiftUI
import DoorstepDropoffSDK
struct ContentView: View {
@State private var deliveryId: String = ""
@State private var placeId: String = ""
@State private var streetNumber: String = ""
@State private var route: String = ""
@State private var subPremise: String = ""
@State private var locality: String = ""
@State private var administrativeArea: String = ""
@State private var postalCode: String = ""
@State private var eventName: String = ""
@State private var statusMessage: String = ""
var body: some View {
NavigationView {
Form {
Section(header: Text("Delivery ID")) {
TextField("Delivery ID", text: $deliveryId)
}
Section(header: Text("Start Delivery")) {
Group {
TextField("Place ID", text: $placeId)
Button("Start by Place ID") {
Task {
do {
try await DoorstepAI.startDeliveryByPlaceID(placeID: placeId, deliveryId: deliveryId)
statusMessage = "Delivery started successfully with Place ID"
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
}
}
Button("Mark Dropoff (POD)") {
Task {
do {
try await DoorstepAI.markDropoff(deliveryId: deliveryId, dropoffType: .pod)
statusMessage = "Dropoff marked"
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
}
}
}
Group {
TextField("Street Number", text: $streetNumber)
TextField("Route", text: $route)
TextField("Sub Premise", text: $subPremise)
TextField("Locality", text: $locality)
TextField("Administrative Area", text: $administrativeArea)
TextField("Postal Code", text: $postalCode)
Button("Start by Address") {
Task {
do {
let address = AddressType(
streetNumber: streetNumber,
route: route,
subPremise: subPremise,
locality: locality,
administrativeAreaLevel1: administrativeArea,
postalCode: postalCode
)
try await DoorstepAI.startDeliveryByAddressType(address: address, deliveryId: deliveryId)
statusMessage = "Delivery started successfully with Address"
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
}
}
}
}
Section(header: Text("Delivery Actions")) {
TextField("Event Name", text: $eventName)
Button("Send Event") {
Task {
do {
// records a custom event on the delivery
try await DoorstepAI.newEvent(eventName: eventName, deliveryId: deliveryId)
statusMessage = "Event sent successfully"
} catch {
statusMessage = "Error: \(error.localizedDescription)"
}
}
}
Button("Stop Delivery") {
Task {
await DoorstepAI.stopDelivery(deliveryId: deliveryId)
statusMessage = "Delivery stopped"
}
}
}
if !statusMessage.isEmpty {
Section(header: Text("Status")) {
Text(statusMessage)
}
}
}
.navigationTitle("DoorstepAI Test")
.onAppear {
DoorstepAI.setApiKey(key: Environment.DOORSTEP_API_KEY)
}
}
}
}
#Preview {
ContentView()
}
Troubleshooting
1. SDK Initialization Errors
Solutions:
// Check API key validity
func validateAPIKey() {
// Ensure API key is not empty or placeholder
guard !apiKey.isEmpty && apiKey != "YOUR_API_KEY_HERE" else {
// Invalid API key
return
}
DoorstepAI.setApiKey(key: apiKey)
}
2. Permission Issues (Location, Motion & Fitness, Bluetooth)
Solutions:
Ask for exactly the bucket you need with DoorstepAI.requestPermissions(_:) — see Request permissions up front in the Usage guide and the staged flow:
import DoorstepDropoffSDK
@MainActor // requestPermissions / checkPermissions are main-actor isolated
func askInStages() {
// Onboarding — foreground location only, no other prompts
DoorstepAI.requestPermissions([.locationWhenInUse])
// Later, after your own "why we need background location" screen
DoorstepAI.requestPermissions([.locationAlways]) { result in
let state = result.statuses[.locationAlways]?.state
print("locationAlways is now \(state?.rawValue ?? "unknown")")
}
}
Then diagnose from the state. DoorstepAI.checkPermissions(_:) / checkPermission(_:) never prompt, so they are safe to call on any screen:
import UIKit
import DoorstepDropoffSDK
@MainActor
func diagnoseLocation() {
switch DoorstepAI.checkPermission(.locationAlways).state {
case .granted:
break // background collection and route geofencing can work
case .whenInUseOnly:
// Foreground location is held, background is NOT. iOS stops delivering
// fixes ~70 s after the screen locks, so a backgrounded delivery collects
// almost nothing and region monitoring cannot run. The upgrade is the
// user's decision: show your rationale, then send them to Settings.
openSettings()
case .permanentlyDenied, .restricted:
// iOS never re-prompts after a refusal — Settings is the only route back.
openSettings()
case .notDeclared:
// The HOST BUILD is missing NSLocationAlwaysAndWhenInUseUsageDescription.
// Fix the Info.plist; this is not a user refusal.
break
case .notDetermined:
DoorstepAI.requestPermissions([.locationAlways])
default:
// `.denied` is never reported on iOS; `.unavailable` means no hardware.
break
}
}
@MainActor
func openSettings() {
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL)
}
}
whenInUseOnly is not a flavour of granted, and it is the common landing spot even after a successful [.locationAlways] call: iOS answers a first-time Always request with the When-In-Use prompt and offers the Always upgrade later on its own schedule.
Order also matters — the location collector requests Always unconditionally when the first startDelivery* builds it, so run your prompts before the first session starts. (startRouteGeofencing is gentler: it asks only when authorization is still notDetermined, and merely warns if you are on When-In-Use.)
More cases, including the missing-Info.plist and empty-requested symptoms: iOS permission troubleshooting.
3. Background Execution Issues
See Best Practices in the Usage guide. The SDK handles backgrounding automatically; don't stop a delivery just because the app is backgrounded.
4. Callback Failures
Solutions:
func handleSDKError(_ error: Error) {
print("SDK error: \(error.localizedDescription)")
// Check for common error patterns
if error.localizedDescription.contains("network") {
// Network connectivity issue; retry or show offline message
} else if error.localizedDescription.contains("permission") {
// Permission issue; read DoorstepAI.checkPermissionsJson() into your log,
// then act on the state (granted / notDetermined / whenInUseOnly /
// permanentlyDenied / restricted / unavailable / notDeclared).
// See /iOS/permissions#troubleshooting
} else if error.localizedDescription.contains("api") {
// API key or authentication issue; check API key validity
}
// Log error for debugging
Logger.error("DoorstepAI SDK Error", metadata: [
"error": "\(error)",
"timestamp": "\(Date())"
])
}
Testing Checklist
Before releasing your iOS integration:
- Delivery IDs are unique and meaningful
- Error handling is implemented for all SDK methods
- App handles background/foreground transitions
-
DoorstepAI.checkPermission(.locationAlways).stateisgranted— notwhenInUseOnly— before you rely on background collection or route geofencing
Support and Resources
Need additional help?
- 📧 Support Email - Direct technical support