You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI表单数据生成PDF并发送至邮箱的实现方法咨询

实现方案

你可以直接用iOS系统自带的邮件发送组件实现需求,不需要额外搭建后端,适合新手快速落地:

前置说明

该方案会调用用户设备上已登录的系统邮件APP发送内容,不需要你处理邮箱账号密码、邮件服务配置等复杂逻辑,测试请用真机,模拟器没有预装邮件APP无法验证功能。

具体实现步骤

1. 封装邮件发送视图

SwiftUI没有原生的邮件发送组件,需要把UIKit的MFMailComposeViewController做一层兼容封装。

2. 格式化表单数据为邮件正文

把表单里的日期、开关、选择器等不同类型的变量统一转成可读的纯文本,作为邮件正文内容。

3. 新增发送按钮和触发逻辑

在导航栏添加发送按钮,点击后自动填充好收件人、主题、正文,用户确认后直接发送即可。

完整修改后代码

import SwiftUI
// 新增:导入邮件框架
import MessageUI

// 新增:封装SwiftUI可用的邮件发送视图
struct MFMailComposeView: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    let subject: String
    let recipients: [String]
    let body: String
    
    func makeUIViewController(context: Context) -> MFMailComposeViewController {
        let vc = MFMailComposeViewController()
        vc.setSubject(subject)
        vc.setToRecipients(recipients)
        vc.setMessageBody(body, isHTML: false)
        vc.mailComposeDelegate = context.coordinator
        return vc
    }
    
    func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(isPresented: $isPresented)
    }
    
    class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
        @Binding var isPresented: Bool
        init(isPresented: Binding<Bool>) {
            _isPresented = isPresented
        }
        func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
            isPresented = false
        }
    }
    
    // 检查设备是否支持邮件发送
    static var canSendMail: Bool {
        MFMailComposeViewController.canSendMail()
    }
}

struct ContentView: View {
    // 原有表单变量不变
    @State private var tournummer = ""
    @State private var fahrernachname = ""
    @State private var fahrervorname = ""
    @State private var fahrergeburtstag = Date()
    @State private var kundenachname = ""
    @State private var kundevorname = ""
    @State private var kundegeburtstag = Date()
    @State private var kilometerstand = ""
    @State private var warnweste = false
    @State private var anzahlschlüßel = 1
    @State private var bereifung = "Winterreifen"
    
    // 新增:控制邮件弹窗显示的变量
    @State private var showMailView = false
    // 新增:邮件发送失败提示变量
    @State private var showMailErrorAlert = false
    
    // 新增:格式化日期的工具
    private var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
    }
    
    // 新增:自动拼接表单数据为邮件正文
    private var mailBody: String {
        """
        工单信息:
        工单编号:\(tournummer)
        
        驾驶员信息:
        姓:\(fahrernachname)
        名:\(fahrervorname)
        生日:\(dateFormatter.string(from: fahrergeburtstag))
        
        客户信息:
        姓:\(kundenachname)
        名:\(kundevorname)
        生日:\(dateFormatter.string(from: kundegeburtstag))
        
        车辆信息:
        公里数:\(kilometerstand)
        
        车辆配置:
        反光背心是否存在:\(warnweste ? "是" : "否")
        钥匙数量:\(anzahlschlüßel)
        轮胎类型:\(bereifung)
        """
    }

    var body: some View {
        NavigationView{
            Form {
                // 原有表单内容完全不变
                Section(header:  Text("Auftrags informationen")){
                    TextField("Tour Nummer", text: $tournummer)
                }
                
                Section(header:  Text("Fahrer Daten")){
                    TextField("Nachname", text: $fahrernachname)
                    TextField("Vorname", text: $fahrervorname)
                    DatePicker("Geburtstag", selection: $fahrergeburtstag, displayedComponents: .date)
                }

                Section(header:  Text("Kunden Daten")){
                    TextField("Nachname", text: $kundenachname)
                    TextField("Vorname", text: $kundevorname)
                    DatePicker("Geburtstag", selection: $kundegeburtstag, displayedComponents: .date)
                }

                Section(header: Text("Fahrzeug Daten")){
                    TextField("Kilometerstand", text: $kilometerstand)
                }
                
                Section(header: Text("Ausstattung")){
                    Toggle("Warnweste Vorhanden", isOn: $warnweste)
                    Stepper("Schlüßel: \(anzahlschlüßel)", value: $anzahlschlüßel, in: 1...5)
                    Picker(
                        selection: $bereifung,
                        label: Text("Bereifung:"),
                        content: {
                            Text("Winterreifen").tag("Winterreifen")
                            Text("Sommerreifen").tag("Sommerreifen")
                            Text("Allwetterreifen").tag("Allwetterreifen")
                        })
                }
                
                Section(header: Text("Fahrzeug Fotos")){
                    Text("fotos hier einfügen omar muss googlen wie das geht hahaha")
                }
            
                Section(header: Text("Hilfe und Service")){
                    Link("..-Logistik Webseite", destination: URL(string: "https://www.logistik....de")!)
                }
                
                Section(header: Text("®..-Logistik")){}
                
            }
            .navigationTitle("MM-Logistik Protokoll")
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing){
                    // 新增:发送按钮
                    Button(action: {
                        hideKeyboard()
                        if MFMailComposeView.canSendMail {
                            showMailView = true
                        } else {
                            showMailErrorAlert = true
                        }
                    }) {
                        Image(systemName: "paperplane.fill")
                    }
                    
                    Button{
                        hideKeyboard()
                    } label: {
                        Image(systemName:"chevron.down")
                    }
                }
            }
            // 新增:绑定邮件弹窗
            .sheet(isPresented: $showMailView) {
                MFMailComposeView(
                    isPresented: $showMailView,
                    subject: "工单数据:\(tournummer.isEmpty ? "未填写编号" : tournummer)",
                    // 替换成你要发送的目标邮箱
                    recipients: ["target@example.com"],
                    body: mailBody
                )
            }
            // 新增:邮件不可用的提示弹窗
            .alert("无法发送邮件", isPresented: $showMailErrorAlert) {
                Button("确定", role: .cancel) {}
            } message: {
                Text("请先在系统邮件APP中登录邮箱账号")
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
            .previewInterfaceOrientation(.portrait)
    }
}

// 原有键盘隐藏逻辑不变
#if canImport(UIKit)
extension View{
    func hideKeyboard(){
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
#endif

后续扩展提示

之后需要支持PDF发送时,只需把生成的PDF文件转成Data格式,调用MFMailComposeViewController的addAttachmentData方法添加为附件即可,现有逻辑无需大幅修改。

内容的提问来源于stack exchange,提问作者Omar Mehrez-Kharat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 22:24:05