Vai al contenuto

Come creare un modulo di richiesta preventivo su iOS con SwiftUI

Un modulo di richiesta in SwiftUI con picker per servizio e budget e una scadenza, che ti invia ogni richiesta via email e manda al cliente una conferma automatica, senza backend.

Aggiornato:

Il video è in inglese; i passaggi qui sotto sono il tutorial completo.

In questo tutorial crei in SwiftUI un modulo di richiesta preventivo con picker nativi: servizio, budget e scadenza, più una descrizione. Tu ricevi un’email strutturata; il cliente riceve una conferma automatica.

Ti serve: un account Frontmail gratuito, un progetto Xcode con SwiftUI e una ventina di minuti.

1. Collega la tua casella e crea i modelli

  1. Servizi email → Aggiungi servizio: collega la tua casella o il tuo provider, Imposta come predefinito, Verifica e salva.
  2. Modelli → Dalla galleria → Richiesta di preventivo (categoria Business) → Usa modello.
  3. Dalla galleria → Risposta automatica: grazie per il messaggio → Usa modello; imposta i valori predefiniti di company_name e response_time, poi selezionalo nel modello di preventivo in Impostazioni → Consegna → Modello di risposta automatica.

Il modello di preventivo richiede name, email, service e details; phone, company, budget e deadline (YYYY-MM-DD) sono facoltativi. service e budget accettano solo i valori della scheda Parametri. L’esempio di codice usa i valori del modello inglese; se crei il modello in italiano, i valori sono Sito web, E-commerce, App mobile, Consulenza, Altro e Meno di 5.000 €, 5.000–20.000 €, 20.000–50.000 €, Oltre 50.000 €. L’app deve inviare esattamente quelle stringhe.

2. Consenti le app mobile e attiva il CAPTCHA

  • Sicurezza → App mobile → Consenti app mobile → Salva.
  • Modello di preventivo → Impostazioni → Protezione → Richiedi CAPTCHA (Turnstile): il widget mobile condiviso di Frontmail, non servono chiavi.

3. Aggiungi il pacchetto

File → Add Package Dependencies… → https://github.com/frontmail-dev/frontmail-swift → aggiungi Frontmail al tuo target e copia TurnstileView dalla sezione Turnstile della documentazione dell’SDK iOS.

4. Crea il modulo

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
        }
    }
}

Le proprietà facoltative che valgono nil non vengono proprio codificate, quindi le righe vuote non compaiono mai nell’email.

5. Provalo

Invia una richiesta usando il tuo indirizzo come email del cliente: ricevi la richiesta e la conferma, ed entrambe compaiono in Cronologia.

Una richiesta con la sua conferma consuma due crediti. Se i crediti finiscono, le richieste vengono accettate come .held e partono automaticamente dopo una ricarica.

Prossimi passi

  • Attiva la Raccolta contatti nelle impostazioni del modello per creare un elenco di tutti quelli che hanno chiesto un preventivo.
  • Allega un brief o delle foto con SendOptions(attachments: [.data(…)]): trovi i dettagli nella documentazione dell’SDK.
Tutti i tutorial