Mfmailcomposeviewcontroller Without Mail App Installed

MFMailComposeViewController without Mail app installed?

On iOS 12 and above, canSendMail() will return true even if the Mail app is not installed. The MFMailComposeViewController will be presented, but the user will be unable to send mail.

On iOS versions below 12:

It looks like MFMailComposeViewController.canSendMail() will return false, and MFMailComposeViewController() will cause a crash (EXC_BAD_ACCESS). Use the canSendMail() check to determine if the device can show a MFMailComposeViewController.

If Mail is installed Do function , if not Open app store page

try this way

 var picker = MFMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
picker.mailComposeDelegate = self
picker.setSubject("Test mail")
picker.setMessageBody(messageBody.text, isHTML: true)
present(picker as? UIViewController ?? UIViewController(), animated: true) { _ in }
}

original post here

Does changing the default email app affect MFMailComposeViewController usage?

iOS 14 and its ability to set a default Mail app did not change anything in regards to MFMailComposeViewController API. It is only capable of displaying Mail's compose sheet, so canSendMail() will still return false when they're not using the Mail app.

To better support users who choose to use a different email app, you could open a mailto URL. This will open the default email app and bring up their compose sheet. If no email app is installed, it will present a system alert asking the user if they want to restore Mail from the App Store (unless running in the Simulator). This API doc explains how you can create the URL, including how to specify a subject, body, and additional recipients.

Note that this will leave your app to open the Mail app or other email app. If you'd like to keep users in your app when they're using Mail, you can continue to use MFMailComposeViewController and fall back to mailto when canSendMail() returns false.

If you would like, you could additionally check if you can open the mailto: URL, and if not, present your own messaging to the user. Note this would require you add mailto to your LSApplicationQueriesSchemes in the Info.plist.

I found this post to be helpful as well.

if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients([email])
mail.setSubject(subject)
present(mail, animated: true, completion: nil)
} else {
if let mailURLString = "mailto:\(email)?subject=\(subject)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let mailURL = URL(string: mailURLString) {
if UIApplication.shared.canOpenURL(mailURL) { //check not needed, but if desired add mailto to LSApplicationQueriesSchemes in Info.plist
view.window?.windowScene?.open(mailURL, options: nil, completionHandler: nil)
} else {
//maybe they like web apps? ‍♂️
//maybe let them copy the email address to the clipboard or something
}
}
}

Send mail without MFMailComposeViewController

This is not supported by the iPhone SDK, probably because Apple doesn't want you to do it.

Why not? My guess: Because it's easy to abuse. If we knew user's email address, we could spam them, we could fake emails from them, etc. Imagine finding out an iPhone app sent an email as you without your knowledge -- not cool, even if the app had good intentions.

So, anything you do to get around that, is likely to get you rejected from the app store.

Having said all that, you could basically write your own smtp interaction code to chat with the user's outgoing mail server. For example, if the user has a gmail account, you could ask them for their email and password, you'd have to know how to talk to the gmail servers, and send email through them.

Going that route means asking the user for their username, password, and either asking for or figuring out their mail server information. Another choice is to send directly from the phone (your own smpt server, not just a client), which is a bit more coding. And if you write your own server, the mail you send is more likely to be blocked since your originating IP might not match the domain on the sender's email.

There also exist some libraries that might help out. Previous related question:

  • Open Source Cocoa/Cocoa-Touch POP3/SMTP library?

How to open the default mail app on iOS 14 without a compose view?

I ended up half-solving it by asking the user with an alert view about their preference, because I did not find a way to query iOS about it directly.

So first am showing an alert view like this:

func askUserForTheirPreference(in presentingViewController: UIViewController) {
let alertController = UIAlertController(title: nil, message: "pleaseConfirmWithApp", preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Apple Mail", style: .default) { action in
self.open(presentingViewController, .applemail)
})
alertController.addAction(UIAlertAction(title: "Google Mail", style: .default) { action in
self.open(presentingViewController, .googlemail)
})
alertController.addAction(UIAlertAction(title: "Microsoft Outlook", style: .default) { action in
self.open(presentingViewController, .outlook)
})
alertController.addAction(UIAlertAction(title: "Protonmail", style: .default) { action in
self.open(presentingViewController, .protonmail)
})
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
os_log("Cancelling", log: Self.log, type: .debug)
})
presentingViewController.present(alertController, animated: true)
}

Then, I am responding to the user's choice like this:

func open(_ presentingViewController: UIViewController, _ appType: AppType) {
switch appType {
case .applemail: UIApplication.shared.open(URL(string: "message:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .googlemail: UIApplication.shared.open(URL(string: "googlegmail:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .outlook: UIApplication.shared.open(URL(string: "ms-outlook:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .protonmail: UIApplication.shared.open(URL(string: "protonmail:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
}
}

private func handleAppOpenCompletion(_ presentingViewController: UIViewController, _ isSuccess: Bool) {
guard isSuccess else {
let alertController = UIAlertController(title: nil, message: "thisAppIsNotInstalled", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .cancel))
presentingViewController.present(alertController, animated: true)
return
}
}

enum AppType {
case applemail, googlemail, outlook, protonmail
}

A clear limitation of this approach is of course that I am limiting the user to very specific apps (in this case Google Mail, iOS "default" Mail, Microsoft Outlook and ProtonMail).
So this approach does not really scale well.

But at least, you can cover a few favorite ones and go from there based on your users' feedback.

The main reason for jumping through these hoops of asking the first is that, at least at the moment, it seems impossible to get that information from iOS directly.
I also could not find a URL scheme that would always open the chosen default Mail app without showing the compose new email view.

MFMailComposeViewController view does not dismiss 2nd time

You need to create a new MFMailComposeViewController each time. Moving your mail declaration inside sendEmail works…

func sendEmail(body: String, subject: String) {
if MFMailComposeViewController.canSendMail() {

// Create a new MFMailComposeViewController…
let mail = MFMailComposeViewController()

mail.mailComposeDelegate = self

mail.setSubject(subject)
mail.setMessageBody("\(body)", isHTML: false)

if let data = (body as NSString).data(using: String.Encoding.utf8.rawValue){
//Attach File
mail.addAttachmentData(data, mimeType: "text/plain", fileName: "data.txt")
}

present(mail, animated: true)
} else {
// show failure alert
}
}

As to why…?



Related Topics



Leave a reply



Submit