Jak zrobić formularz zapytania ofertowego na iOS w SwiftUI
Formularz zapytania ofertowego w SwiftUI z wyborem usługi i budżetu oraz terminem, który wysyła Ci każde zapytanie e-mailem, a klientowi automatyczne potwierdzenie – bez backendu.
Zaktualizowano:
W tym samouczku zbudujesz w SwiftUI formularz zapytania ofertowego z natywnymi pickerami: usługa, budżet i termin, do tego opis. Ty dostajesz uporządkowany e-mail, a klient automatyczne potwierdzenie.
Potrzebujesz: darmowego konta Frontmail, projektu w Xcode ze SwiftUI i około dwudziestu minut.
1. Podłącz skrzynkę i utwórz szablony
- Usługi e-mail → Dodaj usługę: podłącz swoją skrzynkę lub dostawcę, Ustaw jako domyślną, Zweryfikuj i zapisz.
- Szablony → Z galerii → Zapytanie ofertowe (kategoria Biznes) → Użyj szablonu.
- Z galerii → Automatyczna odpowiedź: dziękujemy za wiadomość → Użyj szablonu; ustaw
wartości domyślne
company_nameiresponse_time, a potem wybierz go w szablonie zapytania w Ustawienia → Doręczanie → Szablon automatycznej odpowiedzi.
Szablon zapytania wymaga name, email, service i details; phone, company, budget
i deadline (YYYY-MM-DD) są opcjonalne. service i budget przyjmują tylko wartości z zakładki
Parametry. Przykładowy kod używa wartości z angielskiej wersji szablonu; szablon utworzony po
polsku ma Strona internetowa, Sklep internetowy, Aplikacja mobilna, Konsultacje, Inne oraz Do
20 tys. zł, 20–100 tys. zł, 100–200 tys. zł, Powyżej 200 tys. zł – aplikacja musi wysyłać
dokładnie te same wartości.
2. Zezwól na aplikacje mobilne i włącz CAPTCHA
- Bezpieczeństwo → Aplikacje mobilne → Zezwól na aplikacje mobilne → Zapisz.
- Szablon zapytania → Ustawienia → Ochrona → Wymagaj CAPTCHA (Turnstile) – wspólny mobilny widżet Frontmail, klucze nie są potrzebne.
3. Dodaj pakiet
File → Add Package Dependencies… → https://github.com/frontmail-dev/frontmail-swift → dodaj
Frontmail do swojego targetu i skopiuj TurnstileView z sekcji o Turnstile w dokumentacji
iOS SDK.
4. Zbuduj formularz
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
}
}
}
Opcjonalne właściwości równe nil w ogóle nie są kodowane, więc puste wiersze nigdy nie pojawią się
w e-mailu.
5. Przetestuj
Wyślij zapytanie, podając jako e-mail klienta swój adres: dostaniesz zapytanie i potwierdzenie, a oba pojawią się w Historii.
Zapytanie z potwierdzeniem zużywa dwa kredyty. Jeśli kredyty się skończą, zapytania są przyjmowane jako
.heldi wychodzą automatycznie po doładowaniu.
Co dalej
- Włącz Zbieranie kontaktów w ustawieniach szablonu, żeby budować listę wszystkich, którzy prosili o wycenę.
- Dołączaj brief lub zdjęcia przez
SendOptions(attachments: [.data(…)])– szczegóły w dokumentacji SDK.


