Use SupaHost with iOS and Swift
Connect a SwiftUI app to a SupaHost project with supabase-swift. Add the package, keep keys in an xcconfig, query into Codable models, watch auth state and sign in with Apple or an email code.
supabase-swift is the official Swift client. It uses async/await, decodes rows into your Codable types and stores the session in the Keychain. Point it at your SupaHost project URL with the anon key and it works as it does with any Supabase project.
The examples use the recipes table from the quickstart.
Add the package
In Xcode, choose File → Add Package Dependencies, enter https://github.com/supabase/supabase-swift, pick version 2.x and add the Supabase product to your app target. In a Package.swift:
dependencies: [
.package(url: "https://github.com/supabase/supabase-swift", from: "2.0.0"),
],
targets: [
.target(name: "RecipeBox", dependencies: [
.product(name: "Supabase", package: "supabase-swift"),
]),
]
Keep your keys out of source
Put the Project URL and anon key from the Connect tab (subtab App) in an .xcconfig file that you don't commit, and reference it from Info.plist:
// Secrets.xcconfig ("//" starts a comment, hence the $() in the URL)
SUPABASE_URL = https:/$()/app-one.acme.supahost.space
SUPABASE_ANON_KEY = eyJhbGciOiJIUzI1NiIs...
In Info.plist add two String entries, SUPABASE_URL = $(SUPABASE_URL) and SUPABASE_ANON_KEY = $(SUPABASE_ANON_KEY). The anon key is safe in an app binary. The service_role key must never be in one.
Create the client
// Supabase.swift
import Foundation
import Supabase
let supabase: SupabaseClient = {
let info = Bundle.main.infoDictionary!
return SupabaseClient(
supabaseURL: URL(string: info["SUPABASE_URL"] as! String)!,
supabaseKey: info["SUPABASE_ANON_KEY"] as! String
)
}()
Query a table
Describe a row with a Decodable struct and let the client decode the response:
struct Recipe: Decodable, Identifiable {
let id: Int
let title: String
let minutes: Int?
}
struct RecipeListView: View {
@State private var recipes: [Recipe] = []
@State private var errorText: String?
var body: some View {
List(recipes) { recipe in
Text("\(recipe.title) (\(recipe.minutes ?? 0) min)")
}
.overlay { if let errorText { Text(errorText) } }
.task { await load() }
}
func load() async {
do {
recipes = try await supabase
.from("recipes")
.select("id, title, minutes")
.order("created_at", ascending: false)
.execute()
.value
} catch {
errorText = error.localizedDescription
}
}
}
Inserting uses an Encodable value:
struct NewRecipe: Encodable {
let title: String
let minutes: Int?
}
try await supabase
.from("recipes")
.insert(NewRecipe(title: "Lemon rice", minutes: 30))
.execute()
The insert only succeeds for a signed-in user, because the quickstart's insert policy is for authenticated. Add import SwiftUI to files that define views.
Watch auth state
authStateChanges is an async sequence of events. Use it to switch between your signed-in and signed-out screens:
@MainActor
final class AuthModel: ObservableObject {
@Published var session: Session?
func observe() async {
for await (_, session) in supabase.auth.authStateChanges {
self.session = session
}
}
}
Start it with .task { await auth.observe() } on your root view.
Sign in
Email and password:
try await supabase.auth.signUp(email: email, password: password)
try await supabase.auth.signIn(email: email, password: password)
try await supabase.auth.signOut()
An emailed one-time code works well on iOS and needs no redirect at all. (For links instead, add your app's URL scheme, such as recipebox://login, to Redirect URLs on the Auth tab.)
try await supabase.auth.signInWithOTP(email: email)
// The user reads the code from the email and types it in:
try await supabase.auth.verifyOTP(email: email, token: code, type: .email)
Sign in with Apple
Use AuthenticationServices to get an identity token, then hand it to Supabase. No redirect is involved:
import AuthenticationServices
SignInWithAppleButton { request in
request.requestedScopes = [.email, .fullName]
} onCompletion: { result in
Task {
guard
case .success(let auth) = result,
let credential = auth.credential as? ASAuthorizationAppleIDCredential,
let tokenData = credential.identityToken,
let idToken = String(data: tokenData, encoding: .utf8)
else { return }
try await supabase.auth.signInWithIdToken(
credentials: .init(provider: .apple, idToken: idToken)
)
}
}
Turn on Apple on the project's Auth tab first. See Sign in with Apple for the Apple Developer setup.
For other OAuth providers through a web view, the return address must be an https:// Universal Link your app handles, listed under Redirect URLs on the Auth tab.