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
Spritekit Not Respecting Zposition
Problems with Layout of Some Rows in Swiftui List
Wkwebview Allowslinkpreview to False Breaks Text Selection
Set Custom Font for UItableview Swipe Action (Uicontextualaction)
Restkit Request Not Sending Parameters
How Is a Swift Cgvector Created with Dx and Dy (Derivative)
Uicollectionviewcompositionallayout - Center Items in Sections or Groups
Gkmatchmaker Invite Handler Deprecated
Form Nspredicate from String That Contains Id's
How to Make a Nsurlsesssion Get Request with Cookies
Case Insensitive Matching Search in String Array Swift 3
Raw Depth Map Sdk for iPhone X
How to Lock Viewcontroller in Portrait Mode
How to Implement a "Rate Us" Feature in a Phonegap App
Background Image Not Displaying Correctly in iOS
"Reached the Max Number of Texture Atlases, Can Not Allocate More" Using Google Maps
Get Apple Watch Heartratevariabilitysdnn Realtime
Using Shader Modifiers to Animate Texture in Scenekit Leads to Jittery Textures Over Time