Cómo crear un formulario de solicitud de presupuesto en iOS con SwiftUI
Un formulario de presupuesto en SwiftUI con selectores de servicio y presupuesto y una fecha límite que te envía cada solicitud por correo y manda al cliente una confirmación automática – sin backend.
Actualizado:
En este tutorial crearás un formulario de solicitud de presupuesto en SwiftUI con selectores nativos: servicio, presupuesto y fecha límite, además de una descripción. Tú recibes un correo estructurado; el cliente recibe una confirmación automática.
Necesitas: una cuenta gratuita de Frontmail, un proyecto de Xcode con SwiftUI y unos veinte minutos.
1. Conecta tu buzón y crea las plantillas
- Servicios de correo → Añadir servicio: conecta tu buzón o proveedor, Marcar como predeterminado, Verificar y guardar.
- Plantillas → Desde la galería → Solicitud de presupuesto (categoría Negocios) → Usar plantilla.
- Desde la galería → Respuesta automática: gracias por tu mensaje → Usar plantilla;
define valores por defecto para
company_nameyresponse_timey selecciónala en la plantilla de presupuesto en Ajustes → Entrega → Plantilla de respuesta automática.
La plantilla de presupuesto necesita name, email, service y details; phone, company,
budget y deadline (YYYY-MM-DD) son opcionales. service y budget solo aceptan los valores
de la pestaña Parámetros. El código de ejemplo usa los de la plantilla en inglés; si creas la
plantilla en español, son Sitio web, Tienda online, App móvil, Consultoría, Otro y Menos de
5000 €, 5000–20 000 €, 20 000–50 000 €, Más de 50 000 €. Usa en la app exactamente los mismos
textos.
2. Permite las apps móviles y activa la CAPTCHA
- Seguridad → Apps móviles → Permitir apps móviles → Guardar.
- Plantilla de presupuesto → Ajustes → Protección → Exigir CAPTCHA (Turnstile): el widget móvil compartido de Frontmail, sin necesidad de claves.
3. Añade el paquete
File → Add Package Dependencies… → https://github.com/frontmail-dev/frontmail-swift → añade
Frontmail a tu target y copia TurnstileView de la sección Turnstile de la documentación del
SDK de iOS.
4. Crea el formulario
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
}
}
}
Las propiedades opcionales que son nil no se codifican, así que las filas vacías nunca aparecen
en el correo.
5. Pruébalo
Envía una solicitud con tu propia dirección como correo del cliente: recibirás la solicitud y la confirmación, y ambas aparecerán en Historial.
Una solicitud con su confirmación consume dos créditos. Si se acaban los créditos, las solicitudes se aceptan como
.heldy salen automáticamente tras una recarga.
Próximos pasos
- Activa Recogida de contactos en los ajustes de la plantilla para crear una lista de todas las personas que han pedido presupuesto.
- Adjunta un briefing o fotos con
SendOptions(attachments: [.data(…)]); consulta la documentación del SDK.


