В обычном UIViewController в Swift я использую этот код для отправки почты.
let mailComposeViewController = configuredMailComposeViewController()
mailComposeViewController.navigationItem.leftBarButtonItem?.style = .plain
mailComposeViewController.navigationItem.rightBarButtonItem?.style = .plain
mailComposeViewController.navigationBar.tintColor = UIColor.white
if MFMailComposeViewController.canSendMail() {
self.present(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
Как я могу добиться того же в SwiftUI?
Нужно ли использовать UIViewControllerRepresentable?





Как вы упомянули, вам нужно портировать компонент на SwiftUI через UIViewControllerRepresentable.
Вот простая реализация:
struct MailView: UIViewControllerRepresentable {
@Binding var isShowing: Bool
@Binding var result: Result<MFMailComposeResult, Error>?
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding var isShowing: Bool
@Binding var result: Result<MFMailComposeResult, Error>?
init(isShowing: Binding<Bool>,
result: Binding<Result<MFMailComposeResult, Error>?>) {
_isShowing = isShowing
_result = result
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?) {
defer {
isShowing = false
}
guard error == nil else {
self.result = .failure(error!)
return
}
self.result = .success(result)
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing,
result: $result)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.mailComposeDelegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: MFMailComposeViewController,
context: UIViewControllerRepresentableContext<MailView>) {
}
}
Применение:
struct ContentView: View {
@State var result: Result<MFMailComposeResult, Error>? = nil
@State var isShowingMailView = false
var body: some View {
VStack {
if MFMailComposeViewController.canSendMail() {
Button("Show mail view") {
self.isShowingMailView.toggle()
}
} else {
Text("Can't send emails from this device")
}
if result != nil {
Text("Result: \(String(describing: result))")
.lineLimit(nil)
}
}
.sheet(isPresented: $isShowingMailView) {
MailView(isShowing: self.$isShowingMailView, result: self.$result)
}
}
}
(Проверено на iPhone 7 Plus под управлением iOS 13 — работает отлично)
Обновлено для Xcode 11.4
вау... это так отличается от обычной разработки приложений для iOS с помощью Swift. Спасибо. это работает
Но если вы попробуете дважды, это не сработает. Кажется, есть утечка памяти.
@FlorentMorin result не равен нулю после первого вызова, поэтому он больше не будет отображаться - см. self.isShowingMailView && result == nil
OK. Я предлагаю альтернативу здесь, без почтового контроллера хоста SwiftUI... gist.github.com/florentmorin/4be7ca70c973c29cbeebbed4e2ef20bа
Я обновил свой ответ, включив в него базовый переход, поэтому похоже, что контроллер представления представлен снизу.
Любое обновление о том, как представить это в правильном модальном листе? Я до сих пор могу представить его только один раз. Решение ZStack еще более глючное и выглядит довольно плохо, потому что оно не объединяет представление в модальном стеке iOS 13.
Пожалуйста, смотрите мой ответ ниже. Я изменил приведенный выше код для работы с переменной среды презентации.
Если у кого-то возникла проблема с тем, что всплывающее окно «Сохранить черновик/Удалить черновик» работает с задержкой, а клавиатура скрывает задержку, мне помогло добавить .edgesIgnoringSafeArea(.bottom) на лист MailView, чтобы решить эту проблему.
Он работает, но имеет сбой, например, когда данные передаются пользователю, и мы не можем провести пальцем вниз, чтобы закрыть представление, как это делает реализация UIKit.
Это все еще довольно глючно, окно сообщения случайным образом становится черным. Я вижу похожие проблемы с MFMessageComposeViewController.
Темный режим не работает должным образом, а кнопка отмены невидима (белое в белом)
Ответ @Matteo хорош, но он должен использовать переменную среды презентации. Я обновил его здесь, и он решает все проблемы в комментариях.
import SwiftUI
import UIKit
import MessageUI
struct MailView: UIViewControllerRepresentable {
@Environment(\.presentationMode) var presentation
@Binding var result: Result<MFMailComposeResult, Error>?
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding var presentation: PresentationMode
@Binding var result: Result<MFMailComposeResult, Error>?
init(presentation: Binding<PresentationMode>,
result: Binding<Result<MFMailComposeResult, Error>?>) {
_presentation = presentation
_result = result
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?) {
defer {
$presentation.wrappedValue.dismiss()
}
guard error == nil else {
self.result = .failure(error!)
return
}
self.result = .success(result)
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentation: presentation,
result: $result)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.mailComposeDelegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: MFMailComposeViewController,
context: UIViewControllerRepresentableContext<MailView>) {
}
}
Применение:
import SwiftUI
import MessageUI
struct ContentView: View {
@State var result: Result<MFMailComposeResult, Error>? = nil
@State var isShowingMailView = false
var body: some View {
Button(action: {
self.isShowingMailView.toggle()
}) {
Text("Tap Me")
}
.disabled(!MFMailComposeViewController.canSendMail())
.sheet(isPresented: $isShowingMailView) {
MailView(result: self.$result)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
СПАСИБО за то, что связали все вместе так красиво - это работает хорошо! Если кто-то хочет предварительно заполнить адреса To: и CC: вместе со строкой темы, основным текстом и некоторыми вложенными файлами, пожалуйста, где эти параметры находятся в приведенном выше коде?
@ConfusionTowers Сразу после let vc = MFMailComposeViewController() вы выполняете любую привычную конфигурацию.
.disabled(!MFMailComposeViewController.canSendMail()) не работает. Это вызывало сбои в моем приложении, когда у людей не была настроена почта. В остальном хорошее решение.
Стоит отметить, что я потратил несколько часов, пытаясь заставить это решение работать. (Начиная с SwiftUI 2.0/Xcode 12) принятым ответом было решение, которое работает в моем конкретном случае - с использованием @Binding var isShowing: Bool, а не @Environment(\.presentationMode) var presentation.
vc.setSubject("foo") не работает. Есть идеи?
Если у кого-то возникла проблема с тем, что всплывающее окно «Сохранить черновик/Удалить черновик» отображается с задержкой, а клавиатура скрывает задержку, мне помогло добавить .edgesIgnoringSafeArea(.bottom) на лист MailView, чтобы решить эту проблему.
Он работает, но имеет сбой, например, когда данные передаются пользователю, и мы не можем провести пальцем вниз, чтобы закрыть представление, как это делает реализация UIKit.
К сожалению, после того, как анимация отображения представления завершена, шрифт прыгает с одного размера на другой, подобно тому, как представление обновляется до других настроек после завершения анимации. Я также не могу закрыть представление с помощью действия смахивания (только отмена). Хотя тот же MFMailComposeViewController, представленный с UIKit, работает отлично. Кто-нибудь знает причину и решение?
Ответы правильные Hobbes the Tige & Matteo
Из комментариев, если вам нужно показать предупреждение, если электронная почта не настроена на кнопку или жест касания
@State var isShowingMailView = false
@State var alertNoMail = false
@State var result: Result<MFMailComposeResult, Error>? = nil
HStack {
Image(systemName: "envelope.circle").imageScale(.large)
Text("Contact")
}.onTapGesture {
MFMailComposeViewController.canSendMail() ? self.isShowingMailView.toggle() : self.alertNoMail.toggle()
}
// .disabled(!MFMailComposeViewController.canSendMail())
.sheet(isPresented: $isShowingMailView) {
MailView(result: self.$result)
}
.alert(isPresented: self.$alertNoMail) {
Alert(title: Text("NO MAIL SETUP"))
}
Чтобы предварительно заполнить To, Body ... также я добавляю системный звук, такой же, как звук отправки электронной почты Apple
Параметры: получатели и messageBody могут быть введены при инициализации. Просмотр почты
import AVFoundation
import Foundation
import MessageUI
import SwiftUI
import UIKit
struct MailView: UIViewControllerRepresentable {
@Environment(\.presentationMode) var presentation
@Binding var result: Result<MFMailComposeResult, Error>?
var recipients = [String]()
var messageBody = ""
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding var presentation: PresentationMode
@Binding var result: Result<MFMailComposeResult, Error>?
init(presentation: Binding<PresentationMode>,
result: Binding<Result<MFMailComposeResult, Error>?>)
{
_presentation = presentation
_result = result
}
func mailComposeController(_: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?)
{
defer {
$presentation.wrappedValue.dismiss()
}
guard error == nil else {
self.result = .failure(error!)
return
}
self.result = .success(result)
if result == .sent {
AudioServicesPlayAlertSound(SystemSoundID(1001))
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentation: presentation,
result: $result)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.setToRecipients(recipients)
vc.setMessageBody(messageBody, isHTML: true)
vc.mailComposeDelegate = context.coordinator
return vc
}
func updateUIViewController(_: MFMailComposeViewController,
context _: UIViewControllerRepresentableContext<MailView>) {}
}
Приятное дополнительное прикосновение тщательности!
Здорово! Я хотел бы добавить последний штрих. Как сделать обычный звук при отправке почты?
Я создал для него репозиторий github. просто добавьте его в свой проект и используйте так:
struct ContentView: View {
@State var showMailSheet = false
var body: some View {
NavigationView {
Button(action: {
self.showMailSheet.toggle()
}) {
Text("compose")
}
}
.sheet(isPresented: self.$showMailSheet) {
MailView(isShowing: self.$showMailSheet,
resultHandler: {
value in
switch value {
case .success(let result):
switch result {
case .cancelled:
print("cancelled")
case .failed:
print("failed")
case .saved:
print("saved")
default:
print("sent")
}
case .failure(let error):
print("error: \(error.localizedDescription)")
}
},
subject: "test Subjet",
toRecipients: ["[email protected]"],
ccRecipients: ["[email protected]"],
bccRecipients: ["[email protected]"],
messageBody: "works like a charm!",
isHtml: false)
.safe()
}
}
}
Модификатор safe() проверяет, является ли MFMailComposeViewController.canSendMail()false, автоматически закрывает модальное окно и пытается открыть ссылку mailto.
Ну, у меня есть старый код, который я использовал в SwiftUI таким образом. Статическая функция, принадлежащая этому классу, в основном остается в моем файле Utilities.swift. Но в демонстрационных целях я перенес это сюда.
Кроме того, чтобы сохранить делегат и работать правильно, я использовал его как одноэлементный шаблон.
Шаг 1. Создайте класс помощника по электронной почте
import Foundation
import MessageUI
class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
public static let shared = EmailHelper()
private override init() {
//
}
func sendEmail(subject:String, body:String, to:String){
if !MFMailComposeViewController.canSendMail() {
// Utilities.showErrorBanner(title: "No mail account found", subtitle: "Please setup a mail account")
return //EXIT
}
let picker = MFMailComposeViewController()
picker.setSubject(subject)
picker.setMessageBody(body, isHTML: true)
picker.setToRecipients([to])
picker.mailComposeDelegate = self
EmailHelper.getRootViewController()?.present(picker, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
EmailHelper.getRootViewController()?.dismiss(animated: true, completion: nil)
}
static func getRootViewController() -> UIViewController? {
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController
// OR If you use SwiftUI 2.0 based WindowGroup try this one
// UIApplication.shared.windows.first?.rootViewController
}
}
Шаг 2: Просто вызовите этот способ в классе SwiftUI
Button(action: {
EmailHelper.shared.sendEmail(subject: "Anything...", body: "", to: "")
}) {
Text("Send Email")
}
Я использую это в своем проекте на основе SwiftUI.
Вау это лучшее. Так просто в использовании. Работал отлично. Просто удалите Utilities.showErrorBanner.
К сожалению, это решение приводит к сбою приложения при попытке открыть почтовое приложение дважды подряд. Эта ошибка отображается «[PPT] Ошибка при создании CFMessagePort, необходимого для связи с PPT».
Yeeee @Hobbes, ответ Tige хорош, но ...
Сделаем еще лучше! Что делать, если у пользователя нет Почтовое приложение (как у меня). Вы можете справиться с этим, попробовав другие почтовые приложения.
if MFMailComposeViewController.canSendMail() {
self.showMailView.toggle()
} else if let emailUrl = Utils.createEmailUrl(subject: "Yo, sup?", body: "hot dog") {
UIApplication.shared.open(emailUrl)
} else {
self.alertNoMail.toggle()
}
createEmailURL
static func createEmailUrl(subject: String, body: String) -> URL? {
let to = YOUR_EMAIL
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
return gmailUrl
} else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
return outlookUrl
} else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
return yahooMail
} else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
return sparkUrl
}
return defaultUrl
}
Информация.plist
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlegmail</string>
<string>ms-outlook</string>
<string>readdle-spark</string>
<string>ymail</string>
</array>
Хорошее дополнение! Вы правы, не все используют приложение Apple Mail! Что интересно, у меня не установлено приложение Apple Mail, но MFMailComposeViewController.canSendMail() по-прежнему возвращает значение true.
Хм, странно. Если вы удалили почтовое приложение, оно не должно было вернуть значение true.
это полная копия stackoverflow.com/a/55765362/6898849. пожалуйста, оставьте ссылку на другой ответ, если вы ссылаетесь на него
Это здорово и все такое, но я не могу найти информацию о добавлении файловых вложений для этих сторонних приложений.
Я также улучшил ответ @Hobbes, чтобы упростить настройку таких параметров, как тема, получатели.
Даже лень выписывать суть, тогда как насчет СЗМ?
Теперь вы можете легко копировать и вставлять этот подарок в разные проекты.
Применение;
import SwiftUI
import MessagesUI
// import SwiftUIEKtensions // via SPM
@State private var result: Result<MFMailComposeResult, Error>? = nil
@State private var isShowingMailView = false
var body: some View {
Form {
Button(action: {
if MFMailComposeViewController.canSendMail() {
self.isShowingMailView.toggle()
} else {
print("Can't send emails from this device")
}
if result != nil {
print("Result: \(String(describing: result))")
}
}) {
HStack {
Image(systemName: "envelope")
Text("Contact Us")
}
}
// .disabled(!MFMailComposeViewController.canSendMail())
}
.sheet(isPresented: $isShowingMailView) {
MailView(result: $result) { composer in
composer.setSubject("Secret")
composer.setToRecipients(["[email protected]"])
}
}
}
вытащить композитор (MFMailComposeViewController) — хороший ход; Спасибо!
Я обновил и упростил ответ @Mahmud Assan для нового Жизненный цикл SwiftUI.
import Foundation
import MessageUI
class EmailService: NSObject, MFMailComposeViewControllerDelegate {
public static let shared = EmailService()
func sendEmail(subject:String, body:String, to:String, completion: @escaping (Bool) -> Void){
if MFMailComposeViewController.canSendMail(){
let picker = MFMailComposeViewController()
picker.setSubject(subject)
picker.setMessageBody(body, isHTML: true)
picker.setToRecipients([to])
picker.mailComposeDelegate = self
UIApplication.shared.windows.first?.rootViewController?.present(picker, animated: true, completion: nil)
}
completion(MFMailComposeViewController.canSendMail())
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
Применение:
Button(action: {
EmailService.shared.sendEmail(subject: "hello", body: "this is body", to: "[email protected]") { (isWorked) in
if !isWorked{ //if mail couldn't be presented
// do action
}
}
}, label: {
Text("Send Email")
})
К сожалению, решение @Matteo не работает для меня идеально. Выглядит глючно :(
Альтернативное решение
struct MailComposeSheet<T: View>: UIViewControllerRepresentable {
let view: T
@Binding var isPresented: Bool
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: view)
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {
uiViewController.rootView = view
if isPresented, uiViewController.presentedViewController == nil {
let picker = MFMailComposeViewController()
picker.mailComposeDelegate = context.coordinator
picker.presentationController?.delegate = context.coordinator
uiViewController.present(picker, animated: true)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MFMailComposeViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
var parent: MailComposeSheet
init(_ mailComposeSheet: MailComposeSheet) {
self.parent = mailComposeSheet
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true) { [weak self] in
self?.parent.isPresented = false
}
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
parent.isPresented = false
}
}
}
extension View {
func mailComposeSheet(isPresented: Binding<Bool>) -> some View {
MailComposeSheet(
view: self,
isPresented: isPresented
)
}
}
Применение:
struct ContentView: View {
@State var showEmailComposer = false
var body: some View {
Button("Tap me") {
showEmailComposer = true
}
.mailComposeSheet(isPresented: $showEmailComposer)
}
}
Я новичок в Swift, скажите, пожалуйста, если я делаю что-то не так.
До iOS 14 почтовым приложением по умолчанию на iOS была Mail. Конечно, у вас могли быть установлены другие почтовые приложения.
if MFMailComposeViewController.canSendMail() {
let mailController = MFMailComposeViewController(rootViewController: self)
mailController.setSubject("Test")
mailController.setToRecipients(["[email protected]"])
mailController.mailComposeDelegate = self
present(mailController, animated: true, completion: nil)
}
Сегодня Как разработчик, я хочу уважать выбор пользователем почтового приложения, будь то Mail, Edison, Gmail, Outlook или Hey. Для этого я не могу использовать MFMailComposeViewController. Вместо этого мне нужно добавить почта к ключу LSApplicationQueriesSchemes в Info.plist и затем, когда пользователь хочет отправить электронное письмо, использовать этот код:
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [.universalLinksOnly : false]) { (success) in
// Handle success/failure
}
}
В отличие от MFMailComposeViewController, этот подход отправляет пользователя к выбранному им почтовому приложению и в то же время закрывает исходное приложение. Это не идеально.
Я не вижу необходимости связывать isPresented или результат, поэтому мое предлагаемое решение состоит в том, чтобы использовать обратный вызов при вызове MFMailComposeViewControllerDelegate. Это также делает результат не обнуляемым.
import Foundation
import MessageUI
import SwiftUI
import UIKit
public struct MailView: UIViewControllerRepresentable {
public struct Attachment {
public let data: Data
public let mimeType: String
public let filename: String
public init(data: Data, mimeType: String, filename: String) {
self.data = data
self.mimeType = mimeType
self.filename = filename
}
}
public let onResult: ((Result<MFMailComposeResult, Error>) -> Void)
public let subject: String?
public let message: String?
public let attachment: Attachment?
public class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
public var onResult: ((Result<MFMailComposeResult, Error>) -> Void)
init(onResult: @escaping ((Result<MFMailComposeResult, Error>) -> Void)) {
self.onResult = onResult
}
public func mailComposeController(
_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?
) {
if let error = error {
self.onResult(.failure(error))
} else {
self.onResult(.success(result))
}
}
}
public init(
subject: String? = nil,
message: String? = nil,
attachment: MailView.Attachment? = nil,
onResult: @escaping ((Result<MFMailComposeResult, Error>) -> Void)
) {
self.subject = subject
self.message = message
self.attachment = attachment
self.onResult = onResult
}
public func makeCoordinator() -> Coordinator {
Coordinator(onResult: onResult)
}
public func makeUIViewController(
context: UIViewControllerRepresentableContext<MailView>
) -> MFMailComposeViewController {
let controller = MFMailComposeViewController()
controller.mailComposeDelegate = context.coordinator
if let subject = subject {
controller.setSubject(subject)
}
if let message = message {
controller.setMessageBody(message, isHTML: false)
}
if let attachment = attachment {
controller.addAttachmentData(
attachment.data,
mimeType: attachment.mimeType,
fileName: attachment.filename
)
}
return controller
}
public func updateUIViewController(
_ uiViewController: MFMailComposeViewController,
context: UIViewControllerRepresentableContext<MailView>
) {
// nothing to do here
}
}
Применение
struct ContentView: View {
@State var showEmailComposer = false
var body: some View {
Button("Tap me") {
showEmailComposer = true
}
.sheet(isPresented: $showEmailComposer) {
MailView(
subject: "Email subject",
message: "Message",
attachment: nil,
onResult: { _ in
// Handle the result if needed.
self.showEmailComposer = false
}
)
}
}
}
Для тех, кто, как и я, хочет получить лучшее решение без сбоев экрана пользователя, я нашел очень хорошее решение в этом сообщение от Medium. Решение похоже на ответ @Mahmud Assan, но с дополнительными параметрами приложения электронной почты и предупреждением приложения об ошибке.
Я заменил некоторый код для метода, позволяющего открывать больше почтовых приложений, а не только Mail или Gmail.
Во-первых, не забудьте добавить соответствующую информацию в Информация.plist, в моем случае:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlegmail</string>
<string>ms-outlook</string>
<string>readdle-spark</string>
<string>ymail</string>
</array>
После этого вам нужно создать новый быстрый файл со следующим кодом:
import SwiftUI
import MessageUI
class EmailHelper: NSObject {
/// singleton
static let shared = EmailHelper()
private override init() {}
}
extension EmailHelper {
func send(subject: String, body: String, to: [String]) {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
guard let viewController = windowScene?.windows.first?.rootViewController else {
return
}
if !MFMailComposeViewController.canSendMail() {
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let mails = to.joined(separator: ",")
let alert = UIAlertController(title: "Cannot open Mail!", message: "", preferredStyle: .actionSheet)
var haveExternalMailbox = false
if let url = createEmailUrl(to: mails, subject: subjectEncoded, body: bodyEncoded), UIApplication.shared.canOpenURL(url) {
haveExternalMailbox = true
alert.addAction(UIAlertAction(title: "Gmail", style: .default, handler: { (action) in
UIApplication.shared.open(url)
}))
}
if haveExternalMailbox {
alert.message = "Would you like to open an external mailbox?"
} else {
alert.message = "Please add your mail to Settings before using the mail service."
if let settingsUrl = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(settingsUrl) {
alert.addAction(UIAlertAction(title: "Open Settings App", style: .default, handler: { (action) in
UIApplication.shared.open(settingsUrl)
}))
}
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
viewController.present(alert, animated: true, completion: nil)
return
}
let mailCompose = MFMailComposeViewController()
mailCompose.setSubject(subject)
mailCompose.setMessageBody(body, isHTML: false)
mailCompose.setToRecipients(to)
mailCompose.mailComposeDelegate = self
viewController.present(mailCompose, animated: true, completion: nil)
}
private func createEmailUrl(to: String, subject: String, body: String) -> URL? {
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
return gmailUrl
} else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
return outlookUrl
} else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
return yahooMail
} else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
return sparkUrl
}
return defaultUrl
}
}
// MARK: - MFMailComposeViewControllerDelegate
extension EmailHelper: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
Теперь перейдите к Посмотреть, где вы хотите это реализовать:
struct OpenMailView: View {
var body: some View {
Button("Send email") {
EmailHelper.shared.send(subject: "Help", body: "", to: ["[email protected]"])
}
}
}
Вероятно, это был не вариант, когда вы делали это в 2019 году, но подойдет ли для этого представление «Ссылка»?