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

Swift中从UIActivityViewController唤起带附件的MFMailComposeViewController崩溃问题求助

我来帮你排查这个崩溃问题,你遇到的核心问题是附件数据为nil,再加上UIActivityItemSource的实现存在几个不合理的地方,咱们一步步来解决:

一、先解决PDF数据为nil的问题

崩溃提示里的attachment must not be nil,本质是你通过NSData(contentsOf: receiptURL)读取的PDF数据返回了nil。这里有两个问题要修正:

  1. NSData是OC时代的API,Swift里更推荐用Data
  2. 同步读取文件的操作可能失败(比如路径错误、文件不存在、无权限),必须做安全校验

修正后的PDF读取代码:

guard let receiptURL = receiptURL, let pdfData = try? Data(contentsOf: receiptURL) else {
    // 这里可以给用户弹个提示,或者打印日志排查问题
    print("无法读取PDF文件,请检查路径或权限")
    return
}

二、修正UIActivityItemSource的实现

你当前的扩展实现有几个明显的问题:

  • itemForActivityType返回空UIImage,这对邮件分享毫无意义,邮件需要的是文本内容而非空图片
  • activityViewControllerLinkMetadata里用金额字符串创建URL完全不合理,这会导致元数据异常

修正后的扩展代码:

extension ViewController: UIActivityItemSource {
    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        // 占位符,邮件场景下返回空字符串即可
        return ""
    }
    
    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        // 根据不同的分享类型返回对应内容,比如邮件返回正文
        if activityType == .mail {
            return "这是邮件的正文内容"
        }
        // 其他分享类型(比如短信、AirDrop)可以返回通用文本
        return "分享的收据内容"
    }
    
    @available(iOS 13.0, *)
    func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
        let metadata = LPLinkMetadata()
        metadata.title = "收据分享"
        // 这里替换成实际的文件URL或有效网页URL,没有的话可以直接去掉
        if let receiptURL = receiptURL {
            metadata.originalURL = receiptURL
        }
        // 加载图标时不要加多余的?? "",直接判断UIImage是否存在即可
        if let icon = UIImage(named: "image") {
            metadata.imageProvider = NSItemProvider(object: icon)
        }
        return metadata
    }
}

三、正确构建UIActivityViewController

不需要把self单独放在activityItems里,直接把需要分享的文本、PDF数据和self(作为UIActivityItemSource)组合进去即可:

guard let receiptURL = receiptURL, let pdfData = try? Data(contentsOf: receiptURL) else {
    print("读取PDF失败")
    return
}

let activityItems: [Any] = ["邮件正文内容", pdfData, self]
let activityVC = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = self.view
activityVC.excludedActivityTypes = [UIActivity.ActivityType.copyToPasteboard]
present(activityVC, animated: true)

额外建议:直接使用MFMailComposeViewController(如果只需要邮件分享)

如果你只是想实现邮件带附件的功能,其实可以跳过UIActivityViewController,直接用MFMailComposeViewController,这样更直观也更容易排查问题:

// 先判断设备是否支持发送邮件
if MFMailComposeViewController.canSendMail() {
    let mailVC = MFMailComposeViewController()
    mailVC.mailComposeDelegate = self
    mailVC.setSubject("你的收据")
    mailVC.setMessageBody("这是你需要的收据附件", isHTML: false)
    
    if let receiptURL = receiptURL, let pdfData = try? Data(contentsOf: receiptURL) {
        mailVC.addAttachmentData(pdfData, mimeType: "application/pdf", fileName: "receipt.pdf")
    }
    
    present(mailVC, animated: true)
} else {
    print("设备未配置邮件账号,无法发送邮件")
}

记得补充实现代理方法:

extension ViewController: MFMailComposeViewControllerDelegate {
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        controller.dismiss(animated: true)
    }
}

内容的提问来源于stack exchange,提问作者eloka

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 08:33:17