How to Send OTP in SwiftUI (iOS) — 2026
SwiftUI OTP tutorial using StartMessaging. URLSession, async/await, Observation, iOS keyboard auto-fill, and a clean phone+code flow.
StartMessaging Team
Engineering
iOS OTP login is one of the cleanest UX wins on the platform — the keyboard surfaces the code automatically. This tutorial wires StartMessaging from SwiftUI.
Overview
- URLSession with async/await calls your backend.
- @Observable model holds phone / code / requestId.
- Two SwiftUI views in a NavigationStack.
Network Client
// AuthApi.swift
struct SendResponse: Codable { let requestId: String; let expiresAt: String }
struct VerifyResponse: Codable { let verified: Bool }
actor AuthApi {
let base = URL(string: "https://your-backend.example.com")!
func sendOtp(_ phone: String) async throws -> SendResponse {
var req = URLRequest(url: base.appending(path: "/auth/send-otp"))
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONEncoder().encode(["phoneNumber": phone])
let (d, _) = try await URLSession.shared.data(for: req)
return try JSONDecoder().decode(SendResponse.self, from: d)
}
func verifyOtp(requestId: String, code: String) async throws -> VerifyResponse {
var req = URLRequest(url: base.appending(path: "/auth/verify-otp"))
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONEncoder().encode(["requestId": requestId, "otpCode": code])
let (d, _) = try await URLSession.shared.data(for: req)
return try JSONDecoder().decode(VerifyResponse.self, from: d)
}
}Observable AuthModel
@Observable
class AuthModel {
var phone = ""
var code = ""
var requestId: String?
var error: String?
let api = AuthApi()
func send() async {
do { requestId = try await api.sendOtp(phone).requestId }
catch { self.error = "\(error)" }
}
func verify() async -> Bool {
guard let rid = requestId else { return false }
do { return try await api.verifyOtp(requestId: rid, code: code).verified }
catch { self.error = "\(error)"; return false }
}
}SwiftUI Views
struct LoginView: View {
@State private var model = AuthModel()
var body: some View {
NavigationStack {
if model.requestId == nil {
VStack {
TextField("Phone", text: $model.phone).keyboardType(.phonePad)
Button("Send OTP") { Task { await model.send() } }
}
} else {
VStack {
TextField("OTP", text: $model.code)
.keyboardType(.numberPad)
.textContentType(.oneTimeCode)
Button("Verify") { Task { _ = await model.verify() } }
}
}
}
}
}iOS Auto-Fill
textContentType(.oneTimeCode) is all you need. iOS reads the latest SMS, extracts the code, and surfaces it on the keyboard.
FAQ
UIKit version at our Swift iOS guide.
Related Articles
Verify phone numbers in iOS apps with Swift, calling your backend that proxies the StartMessaging OTP API. Includes URLSession, async/await, and SwiftUI form examples.
Jetpack Compose OTP tutorial using StartMessaging. Retrofit/Ktor client, Compose state, Android SMS Retriever auto-fill, and a clean two-screen flow.
Flutter OTP tutorial using StartMessaging on a Firebase Functions backend. Riverpod state, http client, secure cookie session via Firebase Auth custom token.
Ready to Send OTPs?
Integrate StartMessaging in 5 minutes. No DLT registration required.