Comment créer un formulaire de demande de devis iOS avec SwiftUI
Un formulaire de demande de devis SwiftUI avec sélecteurs de prestation et de budget et une échéance, qui vous envoie chaque demande par e-mail et envoie au client une confirmation automatique – sans backend.
Mis à jour:
Ce tutoriel crée un formulaire de demande de devis en SwiftUI avec des sélecteurs natifs : prestation, budget et échéance, plus une description. Vous recevez un e-mail structuré ; le client reçoit une confirmation automatique.
Il vous faut : un compte Frontmail gratuit, un projet Xcode avec SwiftUI et une vingtaine de minutes.
1. Connectez votre boîte mail et créez les modèles
- Services d’envoi → Ajouter un service : connectez votre boîte mail ou votre fournisseur, Définir par défaut, puis Vérifier et enregistrer.
- Modèles → Depuis la galerie → Demande de devis (catégorie Entreprise) → Utiliser ce modèle.
- Depuis la galerie → Réponse automatique : merci pour votre message → Utiliser ce
modèle ; définissez les valeurs par défaut de
company_nameetresponse_time, puis sélectionnez-le dans le modèle de devis sous Paramètres → Envoi → Modèle de réponse automatique.
Le modèle de devis requiert name, email, service et details ; phone, company, budget
et deadline (YYYY-MM-DD) sont facultatifs. service et budget n’acceptent que les valeurs
listées dans Paramètres. Le code ci-dessous utilise les valeurs du modèle anglais ; si vous
créez le modèle en français, ce sont Site web, Boutique en ligne, Application mobile, Conseil,
Autre et Moins de 5 000 €, 5 000 – 20 000 €, 20 000 – 50 000 €, Plus de 50 000 €.
L’application doit envoyer exactement les chaînes qui y figurent.
2. Autorisez les applications mobiles et activez le CAPTCHA
- Sécurité → Applications mobiles → Autoriser les applications mobiles → Enregistrer.
- Modèle de devis → Paramètres → Protection → Exiger un CAPTCHA (Turnstile) – widget mobile partagé de Frontmail, aucune clé nécessaire.
3. Ajoutez le paquet
File → Add Package Dependencies… → https://github.com/frontmail-dev/frontmail-swift →
ajoutez Frontmail à votre cible, puis copiez TurnstileView depuis la section Turnstile de la
documentation du SDK iOS.
4. Créez le formulaire
import SwiftUI
import Frontmail
let frontmail = try! Frontmail(publicKey: "pk_…")
let services = ["Website", "E-shop", "Mobile app", "Consulting", "Other"]
let budgets = ["Under $5k", "$5k–20k", "$20k–50k", "Over $50k"]
struct QuoteRequest: Encodable {
let name: String
let email: String
let service: String
let budget: String? // nil is left out of the email
let deadline: String? // YYYY-MM-DD
let details: String
}
struct QuoteView: View {
@State private var name = ""
@State private var email = ""
@State private var service = services[0]
@State private var budget = ""
@State private var hasDeadline = false
@State private var deadline = Date()
@State private var details = ""
@State private var widget: Turnstile.Widget?
@State private var token: String?
@State private var resetID = 0
@State private var info = ""
var body: some View {
Form {
Section {
TextField("Name", text: $name)
TextField("Email", text: $email)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
}
Section {
Picker("What do you need?", selection: $service) {
ForEach(services, id: \.self) { Text($0) }
}
Picker("Budget", selection: $budget) {
Text("Not sure yet").tag("")
ForEach(budgets, id: \.self) { Text($0) }
}
Toggle("I have a deadline", isOn: $hasDeadline)
if hasDeadline {
DatePicker("Deadline", selection: $deadline, in: Date()..., displayedComponents: .date)
}
TextField("Project details", text: $details, axis: .vertical)
.lineLimit(4...8)
}
if let widget {
TurnstileView(widget: widget, size: .flexible, resetID: resetID) { event in
if case let .token(t) = event { token = t } else { token = nil }
}
.frame(height: Turnstile.Size.flexible.recommendedHeight)
}
Button("Request a quote") { Task { await send() } }
.disabled(token == nil)
Text(info)
}
.task { widget = try? await Turnstile.widget(client: frontmail) }
}
private func send() async {
guard let widget, let token else { return }
defer { self.token = nil; resetID += 1 } // a token works only once
let request = QuoteRequest(
name: name,
email: email,
service: service,
budget: budget.isEmpty ? nil : budget,
deadline: hasDeadline ? deadline.formatted(Date.ISO8601FormatStyle(timeZone: .current).year().month().day()) : nil,
details: details
)
do {
_ = try await frontmail.send(
templateID: "tpl_…",
params: request,
options: SendOptions(turnstileToken: token, turnstileKey: widget.turnstileKey)
)
info = "Thanks! We’ve emailed you a confirmation."
} catch let error as FrontmailError {
info = error.message
} catch {
info = error.localizedDescription
}
}
}
Les propriétés facultatives à nil ne sont pas encodées du tout : les lignes vides n’apparaissent
jamais dans l’e-mail.
5. Testez
Envoyez une demande avec votre propre adresse comme e-mail du client : vous recevez la demande et la confirmation, et les deux apparaissent dans l’Historique.
Une demande et sa confirmation consomment deux crédits. Si vos crédits s’épuisent, les demandes sont acceptées avec le statut
.heldet partent automatiquement après une recharge.
Étapes suivantes
- Activez la Collecte des contacts dans les paramètres du modèle pour constituer la liste de toutes les personnes qui ont demandé un devis.
- Joignez un cahier des charges ou des photos avec
SendOptions(attachments: [.data(…)])– voir la documentation du SDK.


