ios 我对 Simulator 中的 Swift (iOS8) 中的 MFMailComposeViewController 有真正的误解
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25604552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
I have REAL misunderstanding with MFMailComposeViewController in Swift (iOS8) in Simulator
提问by Alexey Nakhimov
I create a CSV file and try to send it by e-mail. Displays a window to send mail, but is not filled with the body of the email, and no attached file. Application hangs with this screen:
我创建了一个 CSV 文件并尝试通过电子邮件发送它。显示发送邮件的窗口,但没有填写邮件正文,也没有附件。应用程序挂起此屏幕:
button "Cancel" does not work. After a few seconds in the console appears:
“取消”按钮不起作用。几秒钟后在控制台出现:
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
There is my code:
有我的代码:
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!")
}
}
}
Trying instead to send mail using UIActivityViewController
:
尝试使用UIActivityViewController
以下方式发送邮件:
let fileURL: NSURL = NSURL(fileURLWithPath: plistpath)
let actViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
self.presentViewController(actViewController, animated: true, completion: nil)
See approximately the same screen to send e-mail, which after a while returning to the previous screen. In the console, now another error:
看到大致相同的屏幕发送电子邮件,它过一会儿返回到前一个屏幕。在控制台中,现在出现另一个错误:
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
There was something about PlugInKit
.
有一些关于PlugInKit
.
Trying instead UIActivityViewController
using UIDocumentInteractionController
:
尝试UIActivityViewController
使用UIDocumentInteractionController
:
let docController = UIDocumentInteractionController(URL: fileURL)
docController.delegate = self
docController.presentPreviewAnimated(true)
...
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController!) -> UIViewController! {
return self
}
I see this screen with contents a CSV-file:
我看到这个屏幕的内容是一个 CSV 文件:
I press button export in top-right and see this screen:
我按下右上角的按钮导出并看到这个屏幕:
where I choose MAIL and for several seconds I see:
我选择 MAIL 的地方,几秒钟后我看到:
Then returns to displaying the contents of the file! In the console the same messages as when using UIActivityViewController
.
然后返回显示文件的内容!在控制台中的消息与使用时相同UIActivityViewController
。
回答by Fattie
* * IMPORTANT - DO NOT USE THE SIMULATOR FOR THIS. * *
* * 重要 - 请勿为此使用模拟器。* *
Even in 2016, the simulators very simply do not support sending mail from apps.
即使在 2016 年,模拟器也不支持从应用程序发送邮件。
Indeed, the simulators simply do not have mail clients.
事实上,模拟器根本没有邮件客户端。
But! Do see the message at the bottom!
但!请务必查看底部的消息!
Henri has given the total answer. You MUST
亨利给出了完整的答案。你必须
-- allocate and initiate MFMailComposeViewController in an earlier stage, and
-在较早的阶段分配和启动 MFMailComposeViewController,以及
-- hold it in one static variable, and then,
--将它保存在一个静态变量中,然后,
-- whenever it's needed, get the static MFMailComposeViewController instance and use that.
-- 每当需要时,获取静态 MFMailComposeViewController 实例并使用它。
AND you will almost certainly have to cycle the global MFMailComposeViewController after each use. It is notreliable to re-use the same one.
并且您几乎肯定必须在每次使用后循环全局 MFMailComposeViewController。重复使用同一个是不可靠的。
Have a global routine which releases and then re-initializes the singleton MFMailComposeViewController
. Call to that global routine, each time, after you are finished with the mail composer.
有一个全局例程,它释放然后重新初始化单例MFMailComposeViewController
。每次完成邮件编写器后,都调用该全局例程。
Do it in any singleton. Don't forget that your app delegate is, of course, a singleton, so do it there...
在任何单身人士中做到这一点。不要忘记你的应用程序委托当然是一个单身人士,所以在那里做......
@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
.........
}
and...
和...
-(void)cycleTheGlobalMailComposer
{
// cycling GlobalMailComposer due to idiotic iOS issue
self.globalMailComposer = nil;
self.globalMailComposer = [[MFMailComposeViewController alloc] init];
}
Then to use the mail, something like this ...
然后使用邮件,像这样......
-(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, fixed typo per Michael Salamone below.}
{nb,修正了下面 Michael Salamone 的拼写错误。}
Have the following macro in your Prefix file for convenience
为方便起见,在 Prefix 文件中包含以下宏
#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
Also here's a "minor" problem which can cost you days: https://stackoverflow.com/a/17120065/294884
还有一个“小”问题,可能会花费您数天时间:https: //stackoverflow.com/a/17120065/294884
Just for 2016 FTR here's the basic swift code to send an email IN APP,
仅适用于 2016 FTR,这里是在 APP 中发送电子邮件的基本 swift 代码,
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)
}
However! Note!
然而!笔记!
These days it is crappy to send an email "in app".
这些天在“应用程序”中发送电子邮件是糟糕的。
It's much better today to simply cut away to the email client.
今天简单地切换到电子邮件客户端要好得多。
Add to plist ...
添加到 plist ...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
</array>
and then code like
然后像这样编码
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")
}
}
These days, that is much better than trying to email from "inside" the app.
如今,这比尝试从应用程序“内部”发送电子邮件要好得多。
Remember again the iOS simulators simply do not have email clients (nor can you send email using the composer within an app). You must test on a device.
再次记住,iOS 模拟器根本没有电子邮件客户端(您也不能在应用程序中使用 Composer 发送电子邮件)。您必须在设备上进行测试。
回答by Rikkles
It has nothing to do with Swift. It's an issue with the mail composer that's been around forever it seems. That thing is extremely picky, from failing with timeouts to sending delegate messages even when cancelled.
它与 Swift 无关。似乎永远存在的邮件作曲家的问题。这件事非常挑剔,从超时失败到即使取消也发送委托消息。
The workaround everyone uses is to create a global mail composer (for example in a singleton), and every single time reinitializing it when you need it. This ensures the mail composer is always around when the OS needs it, but also that it is free of any crap when you want to reuse it.
每个人都使用的解决方法是创建一个全局邮件编辑器(例如在单例中),并在需要时每次重新初始化它。这确保了邮件编辑器在操作系统需要时始终可用,而且在您想要重用它时也不会出现任何废话。
So create a strong (as global as possible) variable holding the mail composer and reset it every time you want to use it.
So create a strong (as global as possible) variable holding the mail composer and reset it every time you want to use it.
回答by Murolau Murolau
- XCode 6 Simulator has problems managing Mailcomposer and other things.
- Try testing the code with a real device. Likely it will work.
- I have problems when running MailComposer from actionSheet button, also with real test. With IOS 7 worked fine, the same code in IOS 8 does not work. For me Apple must depurated the XCode 6. ( too many different simulated devices with Objective-C and Swift together ...)
- XCode 6 Simulator has problems managing Mailcomposer and other things.
- Try testing the code with a real device. Likely it will work.
- I have problems when running MailComposer from actionSheet button, also with real test. With IOS 7 worked fine, the same code in IOS 8 does not work. For me Apple must depurated the XCode 6. ( too many different simulated devices with Objective-C and Swift together ...)
回答by Michael Salamone
Not sure if the recycling proposed in above solution is necessary or not. But you do need use proper parameters.
Not sure if the recycling proposed in above solution is necessary or not. But you do need use proper parameters.
The delegate receives a MFMailComposeViewController* parameter
. And you need to use that instead of self
when dismissing the controller. I.e.
The delegate receives a MFMailComposeViewController* parameter
. And you need to use that instead of self
when dismissing the controller. I.e.
The delegate receives the (MFMailComposeViewController *) controller
. And you need to use that instead of self
when dismissing the MFMailComposeViewController controller
. That is what you want to dismiss after all.
The delegate receives the (MFMailComposeViewController *) controller
. And you need to use that instead of self
when dismissing the MFMailComposeViewController controller
. That is what you want to dismiss after all.
-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissViewControllerAnimated:YES completion:^
{ [APP cycleTheGlobalMailComposer]; }
];
}
回答by Michal Shatz
Create a property for the mail composer and instantiate it in view did load than call it when ever you need a mail composer.
Create a property for the mail composer and instantiate it in view did load than call it when ever you need a mail composer.
@property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];
回答by Chriss Mejía
Hey this is solved with iOS 8.3 released 2 days ago.
Hey this is solved with iOS 8.3 released 2 days ago.
回答by Sunkas
A simple helper class for handling mail in Swift. Based on Joe Blow's answer.
A simple helper class for handling mail in Swift. Based on Joe Blow's answer.
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()
}
}
}
Place as instance variable in AppDelegate-class and call when needed.
Place as instance variable in AppDelegate-class and call when needed.