У меня REAL непонимание с MFMailComposeViewController в Swift (iOS8) в Simulator
Я создаю файл csv и пытаюсь отправить его по электронной почте. Отображает окно для отправки почты, но не заполнено телом электронной почты и не прикрепленным файлом. Приложение зависает с этим экраном:
![prntscr.com/4ikwwm]()
Кнопка
"Отмена" не работает. Через несколько секунд на консоли появится:
viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=0x7f8409f29b50 {Message=Service Connection Interrupted}
<MFMailComposeRemoteViewController: 0x7f8409c89470> timed out waiting for fence barrier from com.apple.MailCompositionService
Есть мой код:
func actionSheet(actionSheet: UIActionSheet!, clickedButtonAtIndex buttonIndex: Int) {
if buttonIndex == 0 {
println("Export!")
var csvString = NSMutableString()
csvString.appendString("Date;Time;Systolic;Diastolic;Pulse")
for tempValue in results { //result define outside this function
var tempDateTime = NSDate()
tempDateTime = tempValue.datePress
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
var tempDate = dateFormatter.stringFromDate(tempDateTime)
dateFormatter.dateFormat = "HH:mm:ss"
var tempTime = dateFormatter.stringFromDate(tempDateTime)
csvString.appendString("\n\(tempDate);\(tempTime);\(tempValue.sisPress);\(tempValue.diaPress);\(tempValue.hbPress)")
}
let fileManager = (NSFileManager.defaultManager())
let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if ((directorys) != nil) {
let directories:[String] = directorys!;
let dictionary = directories[0];
let plistfile = "bpmonitor.csv"
let plistpath = dictionary.stringByAppendingPathComponent(plistfile);
println("\(plistpath)")
csvString.writeToFile(plistpath, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
var testData: NSData = NSData(contentsOfFile: plistpath)
var myMail: MFMailComposeViewController = MFMailComposeViewController()
if(MFMailComposeViewController.canSendMail()){
myMail = MFMailComposeViewController()
myMail.mailComposeDelegate = self
// set the subject
myMail.setSubject("My report")
//Add some text to the message body
var sentfrom = "Mail sent from BPMonitor"
myMail.setMessageBody(sentfrom, isHTML: true)
myMail.addAttachmentData(testData, mimeType: "text/csv", fileName: "bpmonitor.csv")
//Display the view controller
self.presentViewController(myMail, animated: true, completion: nil)
}
else {
var alert = UIAlertController(title: "Alert", message: "Your device cannot send emails", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
else {
println("File system error!")
}
}
}
Попытка отправить почту с помощью UIActivityViewController
:
let fileURL: NSURL = NSURL(fileURLWithPath: plistpath)
let actViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
self.presentViewController(actViewController, animated: true, completion: nil)
См. примерно тот же экран для отправки электронной почты, который через некоторое время возвращается к предыдущему экрану. В консоли теперь появилась еще одна ошибка:
viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=0x7faab3296ad0 {Message=Service Connection Interrupted}
Errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo=0x7faab3005890 {NSLocalizedDescription=query cancelled}
<MFMailComposeRemoteViewController: 0x7faab3147dc0> timed out waiting for fence barrier from com.apple.MailCompositionService
В PlugInKit
было что-то.
Попытка вместо UIActivityViewController
использовать UIDocumentInteractionController
:
let docController = UIDocumentInteractionController(URL: fileURL)
docController.delegate = self
docController.presentPreviewAnimated(true)
...
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController!) -> UIViewController! {
return self
}
Я вижу этот экран с содержимым CSV файла: http://prntscr.com/4ilgax Я нажимаю кнопку export в правом верхнем углу и вижу этот экран http://prntscr.com/4ilguk, где я выбираю MAIL, и в течение нескольких секунд я вижу http://prntscr.com/4ilh2h Затем возвращается к отображению содержимого файла! В консоли те же сообщения, что и при использовании UIActivityViewController
.
Ответы
Ответ 1
* * ВАЖНО - НЕ ИСПОЛЬЗУЙТЕ СИМУЛЯТОР ДЛЯ ЭТОГО. * *
Даже в 2016 году симуляторы очень просто не поддерживают отправку почты из приложений.
Действительно, у симуляторов просто нет почтовых клиентов.
Но! Посмотрите сообщение внизу!
Генри дал полный ответ. Вы ДОЛЖНЫ
- выделять и запускать MFMailComposeViewController на более раннем этапе и
- удерживайте его в одной статической переменной, а затем
- всякий раз, когда это необходимо, получите статический экземпляр MFMailComposeViewController и используйте его.
И вам почти наверняка придется циклически запускать глобальный MFMailComposeViewController после каждого использования. Нельзя повторно использовать один и тот же.
У вас есть глобальная подпрограмма, которая освобождает, а затем повторно инициализирует singleton MFMailComposeViewController
. Вызовите эту глобальную процедуру каждый раз, после того как вы закончите с композитором почты.
Сделайте это в любом синглетоне. Не забывайте, что ваш делегат приложения, конечно, синглтон, так что сделайте это там...
@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer;
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
........
// part 3, our own setup
[self cycleTheGlobalMailComposer];
// needed due to the worst programming in the history of Apple
.........
}
и...
-(void)cycleTheGlobalMailComposer
{
// cycling GlobalMailComposer due to idiotic iOS issue
self.globalMailComposer = nil;
self.globalMailComposer = [[MFMailComposeViewController alloc] init];
}
Затем, чтобы использовать почту, что-то вроде этого...
-(void)helpEmail
{
// APP.globalMailComposer IS READY TO USE from app launch.
// recycle it AFTER OUR USE.
if ( [MFMailComposeViewController canSendMail] )
{
[APP.globalMailComposer setToRecipients:
[NSArray arrayWithObjects: emailAddressNSString, nil] ];
[APP.globalMailComposer setSubject:subject];
[APP.globalMailComposer setMessageBody:msg isHTML:NO];
APP.globalMailComposer.mailComposeDelegate = self;
[self presentViewController:APP.globalMailComposer
animated:YES completion:nil];
}
else
{
[UIAlertView ok:@"Unable to mail. No email on this device?"];
[APP cycleTheGlobalMailComposer];
}
}
-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissViewControllerAnimated:YES completion:^
{ [APP cycleTheGlobalMailComposer]; }
];
}
{nb, фиксированная опечатка на Майкла Саламона ниже.}
У вас есть следующий макрос в вашем префиксном файле
#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
Также здесь "незначительная" проблема, которая может стоить вам дней: fooobar.com/questions/99537/...
Только для 2016 FTR здесь базовый быстрый код для отправки электронной почты IN APP,
class YourClass:UIViewController, MFMailComposeViewControllerDelegate
{
func clickedMetrieArrow()
{
print("click arrow! v1")
let e = MFMailComposeViewController()
e.mailComposeDelegate = self
e.setToRecipients( ["[email protected]"] )
e.setSubject("Blah subject")
e.setMessageBody("Blah text", isHTML: false)
presentViewController(e, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
{
dismissViewControllerAnimated(true, completion: nil)
}
Однако! Внимание!
В эти дни дрянной отправить электронное письмо "в приложении".
Намного лучше сегодня просто отключить почтовый клиент.
Добавить в plist...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
</array>
а затем введите код как
func pointlessMarketingEmailForClient()
{
let subject = "Some subject"
let body = "Plenty of <i>email</i> body."
let coded = "mailto:[email protected]?subject=\(subject)&body=\(body)".stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())
if let emailURL:NSURL = NSURL(string: coded!)
{
if UIApplication.sharedApplication().canOpenURL(emailURL)
{
UIApplication.sharedApplication().openURL(emailURL)
}
else
{
print("fail A")
}
}
else
{
print("fail B")
}
}
В наши дни это намного лучше, чем пытаться отправить по электронной почте из "внутри" приложения.
Помните, что у iOS-симуляторов просто нет почтовых клиентов (и вы не можете отправлять электронную почту с помощью композитора в приложении). Вы должны протестировать устройство.
Ответ 2
Это не имеет никакого отношения к Swift. Это проблема с почтовым композитором, которая была вокруг навсегда. Эта вещь чрезвычайно придирчива, от сбоев с таймаутами до отправки сообщений делегатов даже в случае отмены.
Обходной путь, который каждый использует, - создать глобальный почтовый композитор (например, в одноэлементном) и каждый раз повторить его инициализацию, когда вам это нужно. Это гарантирует, что почтовый композитор всегда находится вокруг, когда ему это нужно, но также и что он свободен от всякого дерьма, когда вы хотите его повторно использовать.
Итак, создайте сильную (как можно более глобальную) переменную, содержащую почтовый композитор и reset, каждый раз, когда вы хотите его использовать.
Ответ 3
- XCode 6 Simulator имеет проблемы с управлением Mailcomposer и другими вещами.
- Попробуйте протестировать код с помощью реального устройства. Вероятно, он будет работать.
- У меня возникают проблемы при запуске MailComposer с кнопки ActionSheet, а также с реальным тестом. С IOS 7 работал нормально, тот же код в IOS 8 не работает. Для меня Apple должна уничтожить XCode 6. (слишком много разных имитируемых устройств с Objective-C и Swift вместе...)
Ответ 4
Не уверен, нужна ли рециркуляция, предложенная в вышеуказанном решении, или нет. Но вам нужны правильные параметры.
Делегат получает MFMailComposeViewController* parameter
. И вы должны использовать это вместо self
при отключении контроллера. То есть.
Делегат получает (MFMailComposeViewController *) controller
. И вы должны использовать это вместо self
при отклонении MFMailComposeViewController controller
. Это то, что вы хотите уволить в конце концов.
-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissViewControllerAnimated:YES completion:^
{ [APP cycleTheGlobalMailComposer]; }
];
}
Ответ 5
Создайте свойство для композитора почты и создайте экземпляр его в режиме загрузки, а не вызовите его, когда вам понадобится почтовый композитор.
@property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];
Ответ 6
Привет, это решено с выпуском iOS 8.3, выпущенным 2 дня назад.
Ответ 7
Простой вспомогательный класс для обработки почты в Swift. На основании ответа Джо Блау.
import UIKit
import MessageUI
public class EmailManager : NSObject, MFMailComposeViewControllerDelegate
{
var mailComposeViewController: MFMailComposeViewController?
public override init()
{
mailComposeViewController = MFMailComposeViewController()
}
private func cycleMailComposer()
{
mailComposeViewController = nil
mailComposeViewController = MFMailComposeViewController()
}
public func sendMailTo(emailList:[String], subject:String, body:String, fromViewController:UIViewController)
{
if MFMailComposeViewController.canSendMail() {
mailComposeViewController!.setSubject(subject)
mailComposeViewController!.setMessageBody(body, isHTML: false)
mailComposeViewController!.setToRecipients(emailList)
mailComposeViewController?.mailComposeDelegate = self
fromViewController.presentViewController(mailComposeViewController!, animated: true, completion: nil)
}
else {
print("Could not open email app")
}
}
public func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
{
controller.dismissViewControllerAnimated(true) { () -> Void in
self.cycleMailComposer()
}
}
}
Поместите в качестве экземпляра переменную в класс AppDelegate и вызовите при необходимости.