Tutorials

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 Updated

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.

S

StartMessaging Team

StartMessaging Team

Related posts