Swift中从UIActivityViewController唤起带附件的MFMailComposeViewController崩溃问题求助
我来帮你排查这个崩溃问题,你遇到的核心问题是附件数据为nil,再加上UIActivityItemSource的实现存在几个不合理的地方,咱们一步步来解决:
一、先解决PDF数据为nil的问题
崩溃提示里的attachment must not be nil,本质是你通过NSData(contentsOf: receiptURL)读取的PDF数据返回了nil。这里有两个问题要修正:
NSData是OC时代的API,Swift里更推荐用Data- 同步读取文件的操作可能失败(比如路径错误、文件不存在、无权限),必须做安全校验
修正后的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
相关产品推荐
相关产品推荐

