MessageComposeViewController not calling delegate - ios

I've been searching for solutions to my problem without any success...
The app I'm developing lets the user play a small quiz game and send the result as a text message. Everything works fine except when the MessageComposeViewController is suppose to dismiss (on send/cancel).
It seems like the MessageComposeViewController doesn't call the delegate since I don't get the print from the delegate function...
I have a separate class called SendMessage which handles the MessageComposeViewController, when the user click a button "Send" in a ViewController I create an instance of this class and present it.
Part of my ViewController with the send button:
#IBAction func Send(_ sender: Any) {
let sendResult = SendMessage()
if sendResult.canSend() {
let meddelande = sendResult.createMessage(result: 8, name: "Steve Jobs")
present(meddelande, animated: true, completion: nil)
} else {
alert.addAction(alertButton)
self.present(alert, animated: true, completion: nil)
}
}
The class which handles the MessageComposeViewController called SendMessage (I left some irrelevant code out)
func createMessage(result: Int, name: String) -> MFMessageComposeViewController {
let meddelande = MFMessageComposeViewController()
meddelande.messageComposeDelegate = self
meddelande.recipients = ["PhoneNumber"]
meddelande.body = name + ": " + String(result)
return meddelande
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
print ("F*ck")
controller.dismiss(animated: true, completion: nil)
}
Grateful for any help!

I think you should hold a strong reference to it instead of a local variable
let sendResult = SendMessage()
declare it as instance variable
var sendResult:SendMessage?

Related

MFMailComposeViewController delegate not working on swift 4

I'm trying to dismiss the MFMailComposeViewController but the delegate is not triggered. It seems that it's a common issue and the answers are the same, but they do not work for me :(. I have a button that calls the function to send the mail. I first create a csv file, then the MFMailComposeViewController and attach the csv file to the mail. The mail is sent sometimes (the mail view controller does not dismiss after that) and the cancel button shows me the option to delete or save the draft but after that nothing happens.
Here's the code of the button:
import UIKit
import MessageUI
class UserInfoViewController: UIViewController, MFMailComposeViewControllerDelegate {
#IBAction func uploadPressed(_ sender: Any) {
let contentsOfFile = "Name,ID,Age,Sex,Time,\n\(name),\(id),\(age),\(sex),\(time)"
let data = contentsOfFile.data(using: String.Encoding.utf8, allowLossyConversion: false)
if let content = data {
print("NSData: \(content)")
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
let emailController = MFMailComposeViewController()
//emailController.mailComposeDelegate = self as? MFMailComposeViewControllerDelegate
emailController.mailComposeDelegate = self
emailController.setToRecipients([""])
emailController.setSubject("CSV File")
emailController.setMessageBody("", isHTML: false)
emailController.addAttachmentData(data!, mimeType: "text/csv", fileName: "registro.csv")
return emailController
}
let emailViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.present(emailViewController, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
print("Delegate worked!")
controller.dismiss(animated: true, completion: nil)
}
}
}
Thank you very much in advance.
Your issue is being caused by putting the delegate method inside another method. You can't do that. Delegate functions need to be at the top level of the class. Simply move your mailComposeController(_:didFinishWith:error:) function out of the uploadPressed function.

Page created by MFMailComposeViewController could not be shown because of hierarchy - Swift 4

I attempt to add a function, that is a mail page would pop up after the user touched a row in a table. Namely, it means that the user could activate a "function" (here the name of that function is "orderOfSendAnEmailToReportTheProblem") when the row is tapped. All of my codes were shown below. (This kind of code has been proposed by several genii on Stackoverflow...)
import Foundation
import UIKit
import MessageUI
class ReportProblem : UIViewController, MFMailComposeViewControllerDelegate {
func orderOfSendAnEmailToReportTheProblem() {
let mailComposeViewController = configureMailController()
if MFMailComposeViewController.canSendMail() {
self.present(mailComposeViewController, animated: true, completion: nil)
} else {
showMailError()
}
}
//Activate the series of the commands of sending the email.
func configureMailController() -> MFMailComposeViewController {
let mailComposeVC = MFMailComposeViewController()
mailComposeVC.mailComposeDelegate = self
mailComposeVC.setToRecipients(["my email"])
mailComposeVC.setSubject("Yo")
return mailComposeVC
}
//Set the recipient and the title of this email automatically.
func showMailError() {
let sendMailErrorAlert = UIAlertController(title: "Could not sned the email.", message: "Oops, something was wrong, please check your internet connection once again.", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
sendMailErrorAlert.addAction(dismiss)
self.present(sendMailErrorAlert, animated: true, completion: nil) //If you conform the protocol of NSObject instead of UIViewController, you could not finish this line successfully.
}
//Set a alert window so that it would remind the user when the device could not send the email successfully.
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
//Set this final step so that the device would go to the previous window when you finish sending the email.
}
However, a problem occurred. When I test it on my real device, and after I tapped that particular row, nothing happened, no any new page pop up... The Xcode only showed that "Warning: Attempt to present on whose view is not in the window hierarchy!" I have tried several ways, such as "view.bringSubview(toFront: mailComposeVC)" or adding the codes shown below at the end of my codes, but nothing worked.
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
I noticed that some other people also would face similar problems when they want to create the alert window, and the solution of that is to create an independent UIWindow, but I want to use mailComposeController to present the email page instead. Some others also faced some problems about MFMailComposeViewController, but their problems are not concerning to hierarchy. I was a novice of swift, and I was haunted by this problem for a whole day... I used swift 4 to develop my App, is anyone know how to solve this problem here?...
So now I'm writing another way to present which I'm using for generic views.
Have Some code in another class for presentation of view so that you can reuse them throughout the app with these two methods.
func slideInFromRight(parentView:UIView,childView:UIView) {
childView.transform = CGAffineTransform(translationX: parentView.frame.maxX, y: 0)
parentView.addSubview(childView)
UIView.animate(withDuration: 0.25, animations: {
childView.transform = CGAffineTransform(translationX: 0, y: 0)
})
}
func slideOutToRight(view:UIView) {
UIView.animate(withDuration: 0.25, animations: {
view.transform = CGAffineTransform(translationX: view.frame.maxX, y: 0)
},completion:{(completed:Bool) in
view.removeFromSuperview()
})
}
Now use these methods to present and remove custom view controller as follows
let window = UIApplication.shared.keyWindow
let vc = YourViewController().instantiate()
self.addChildViewController(vc)
let view = vc.view
view.frame = CGRect(x: 0, y: 20, width: window!.frame.width, height: window!.frame.height-20)
//Here Animation is my custom presenter class and shared is it's shared instance.
Animation.shared.slideInFromRight(parentView: window!, childView: view)
//Or you can use current View controller's view
Animation.shared.slideInFromRight(parentView: self.view!, childView: view)
Genius Vivek Singh, your way looks good, but it's a little bit tedious. Moreover, it still did not work in my project... (It seems that you used some codes about UIView, such as parentView, childView, and view. However, I used MFMailComposeViewController which seems is a little bit different from original view...I am not sure whether this theory is correct or not...)
However, I have found the solution. I presume that the problem is that after the user clicked the row in another tableViewController (here is SettingTVController), it would activate the function "orderOfSendAnEmailToReportTheProblem( )" which is in "another " viewController (here is ReportProblem). Because there are two different viewController, some kind of conflict occurred.
Therefore, I move my whole codes I posted in the above question to my original tableViewController, so that the user would not go into another viewController when they activate the function, and there's no hierarchy problem anymore.
import UIKit
import StoreKit
import MessageUI
class SettingTVController: UITableViewController, MFMailComposeViewControllerDelegate {
var settingTitleConnection = showData()
override func viewDidLoad() {
//skip
}
override func didReceiveMemoryWarning() {
//skip
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//skip
}
override func numberOfSections(in tableView: UITableView) -> Int {
//skip
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//skip
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.indexPathForSelectedRow?.row == 2 && tableView.indexPathForSelectedRow?.section == 1 {
orderOfSendAnEmailToReportTheProblem()
} else {
//skip
}
tableView.deselectRow(at: indexPath, animated: true)
}
//-----<The codes below is used to construct the function of reporting problem with email>-----
func orderOfSendAnEmailToReportTheProblem() {
let mailComposeViewController = configureMailController()
self.present(mailComposeViewController, animated: true, completion: nil)
if MFMailComposeViewController.canSendMail() {
self.present(mailComposeViewController, animated: false, completion: nil)
} else {
showMailError()
}
}
//Activate the series of the commands of sending the email.
func configureMailController() -> MFMailComposeViewController {
let mailComposeVC = MFMailComposeViewController()
mailComposeVC.mailComposeDelegate = self
mailComposeVC.setToRecipients(["datototest#icloud.com"])
mailComposeVC.setSubject("Reporting of Problems of Rolling")
return mailComposeVC
}
//Set the recipient and the title of this email automatically.
func showMailError() {
let sendMailErrorAlert = UIAlertController(title: "Could not send the email.", message: "Oops, something was wrong, please check your internet connection once again.", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
sendMailErrorAlert.addAction(dismiss)
self.present(sendMailErrorAlert, animated: true, completion: nil) //If you conform the protocol of NSObject instead of UIViewController, you could not finish this line successfully.
}
//Set a alert window so that it would remind the user when the device could not send the email successfully.
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
//UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: nil)
}
//Set this final step so that the device would go to the previous window when you finish sending the email.
//-----<The codes above is used to construct the function of reporting problem with email>-----
}
I posted my codes above so that it may help others who face similar problem someday. Once again, thanks for your help!!
I don't know why you are facing a view hierarchy issue. But I am able to achieve the mail share option in swift 4. I followed exactly same steps.
Check if mail can be sent:
MFMailComposeViewController.canSendMail()
Configure mail body:
private func configureMailController() -> MFMailComposeViewController {
let mailComposeViewController = MFMailComposeViewController()
mailComposeViewController.mailComposeDelegate = self
mailComposeViewController.setMessageBody("MESSAGE BODY", isHTML: true)
return mailComposeViewController
}
Present mail VC:
present(mailComposeViewController, animated: true)
confirm optional protocol and dismiss the view explicitly:
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true)
}

Navigate to a UIViewController from a UIAlertView in Swift 2.3

I am new to Swift and iOS development. I have been trying to create an app-wide check for internet connectivity, that allows the user to retry (reload the current view). The checkInternetConnection() function below is called during viewWillAppear() in my BaseUIViewController class.
The Reachability.isConnectedToNetwork() connectivity-check works fine. But so far, I have not been able to figure out a way to reload the current view after the user presses the 'Retry' button in the alert. in fact, I have not been able to figure out a way to reload the current view in any scenario.
One thing I know I am doing wrong in the following attempt (since there is a compile error telling me so), is that I am passing a UIViewController object as a parameter instead of a string on this line: self.performSegueWithIdentifier(activeViewController, sender:self), but I suspect that this is not my only mistake.
func checkInternetConnection() {
if (Reachability.isConnectedToNetwork()) {
print("Internet connection OK")
} else {
print("Internet connection FAILED")
let alert = UIAlertController(title: NSLocalizedString("Error!", comment: "Connect: error"), message: NSLocalizedString("Could not connect", comment: "Connect: error message"), preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: "Connect: retry"), style: .Default, handler: { action in
print("Connection: Retrying")
let navigationController = UIApplication.sharedApplication().windows[0].rootViewController as! UINavigationController
let activeViewController: UIViewController = navigationController.visibleViewController!
self.performSegueWithIdentifier(activeViewController, sender:self)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
BaseUIViewController can perform app-wide check for connectivity, so all your ViewControllers will be inherited from BaseUIViewController.
And each ViewController will have different behaviour after connectivity check.
One thing you can do is, in your BaseUIViewController you can define a block that performs action after connectivity check failed.
Here's the example:
class BaseViewUIController: UIViewController {
var connectivityBlock: (() -> Void)?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
checkInternetConnection()
}
func checkInternetConnection() {
if Reachability.isConnectedToNetwork() {
print("Internet connection OK")
} else {
if let b = connectivityBlock {
b()
}
}
}
}
And in your inherited ViewControllers:
class MyViewController: BaseUIViewController {
override func viewDidLoad() {
super.viewDidLoad()
connectivityBlock = {
print("Do View Refresh Here.")
}
}
}
Then After the check in BaseUIViewController, the code in connectivityBlock will be executed so that each VC can deal with it differently.
You can also define a function in BaseUIViewController and each subclass will override that function, after connectivity check failed, call the function.
Try navigate using application's windows
let destinationVC = UIApplication.shared.delegate?.window
destinationVC .present(activeViewController, animated: true, completion: nil)

CNContactViewController Cancel Button Not Working

I'm trying to use the built-in new contact UI and am getting unexpected behavior with the cancel button. The code below works and calls up the new contact screen but the cancel button will only clear the screen entries not cancel out of the new contact screen. In the built in contacts app hitting cancel returns to the contact list screen. I would like the cancel button to close out the window.
#IBAction func newTwo(sender: AnyObject) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let npvc = CNContactViewController(forNewContact: nil)
npvc.delegate = self
self.navigationController?.pushViewController(npvc, animated: true)
}
}
}
did you implement CNContactViewControllerDelegate methods?
Here's a link to documentation
for example:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
It worked for me using the following code:
Swift 3
func contactViewController(_ vc: CNContactViewController, didCompleteWith con: CNContact?) {
vc.dismiss(animated: true)
}
Also I changed the way I was calling the controller:
Instead of:
self.navigationController?.pushViewController(contactViewController, animated: true)
the solution was:
self.present(UINavigationController(rootViewController: contactViewController), animated:true)
I found the solution using the example code Programming-iOS-Book-Examples written by Matt Neuburg:
Better way to do the dismissing would be to check if the contact is nil and then dismiss it. The dismiss doesn't work if you've pushed the view controller from a navigation controller. You might have to do the following:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
if let contactCreated = contact
{
}
else
{
_ = self.navigationController?.popViewController(animated: true)
}
}

how to wait for ui delegate in swift

I want to create simple object helper to sending sms in many places on my Swift 2.0 app, and in next step another helpers (email, pdf opener etc)
I create simple class:
import Foundation
import MessageUI
class SmsHelper: MFMessageComposeViewControllerDelegate {
func sendSMS(body: String){
if (MFMessageComposeViewController.canSendText()){
let messageVC = MFMessageComposeViewController()
messageVC.body = body
//messageVC.recipients = ["Enter tel-nr"]
messageVC.messageComposeDelegate = self;
AppDelegate().sharedInstance().getTopController().presentViewController(messageVC, animated: false, completion: nil)
}
else{
//do some alert etc.
}
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult){
sendSMSRsp(result, errorMsg: nil)
controller.dismissViewControllerAnimated(true, completion: nil)
print("sms didFinishWithResult")
}
}
in anywhere in code I want to do that:
class someAnotherClass{
func someFunction(){
let smsHelper = SmsHelper()
smsHelper.sendSms("some text")
}
}
so sms ios editor is opened, but when I want to close it or sending, it don't dismiss, function messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) i never called and app crashes with memory leak, I know reason: is because SmsHelper object in 'someFunction' is deleting after end scope of this function, and this object is nil, and system try to call didFinishWithResult at nil object. I confirm it: when I add smsHelper object as member of 'SomeClass' it worked - delegate is called.
Question is: What is the best practice to do that, adding a member is not an option for me, because many classes can use that, also creating a singleton, appDelegate member is I think quite stupid. How to force not deleting a object at end of scope function?
Try to use singleton way to do it:
class SmsHelper: MFMessageComposeViewControllerDelegate {
static let sharedInstance = SmsHelper()
private init() {}
}
And usage:
SmsHelper.sharedInstance().sendSms('some text')
EDIT:
If you add different init method from abstract class.
private override init(message: String){
super.init()
}
then change definition to static let sharedInstance = SmsHelper(message:"smsHelper") won't complicated.
For your delegate, just assign it out of the init method, in the UIViewController viewDidLoad,
SmsHelper.sharedInstance().delegate = self
For me, the way to do this is to create a type alis of a closure, then storing an optional variable of this type alias. When you call sendSMS, you pass in a closure and set it to your variable. Then that can be called in the delegate, which will then pass it through the closure of sendSMS:
import Foundation
import MessageUI
class SmsHelper: MFMessageComposeViewControllerDelegate {
let messageVC: MFMessageComposeViewController!
typealias SMSCompletion = (result: MessageComposeResult, errorMsg: NSError?) -> ()
var sendSMSRsp: SMSCompletion?
func sendSMS(body: String, completion: SMSCompletion){
sendSMSRsp = completion
if (MFMessageComposeViewController.canSendText()){
messageVC = MFMessageComposeViewController()
messageVC.body = body
//messageVC.recipients = ["Enter tel-nr"]
messageVC.messageComposeDelegate = self;
AppDelegate().sharedInstance().getTopController().presentViewController(messageVC, animated: false, completion: nil)
} else {
//do some alert etc.
}
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult){
sendSMSRsp?(result: result, errorMsg: nil)
controller.dismissViewControllerAnimated(true, completion: nil)
print("sms didFinishWithResult")
}
}
class SomeOtherClass {
func someFunction() {
let smsHelper = SmsHelper()
smsHelper.sendSMS("some text") { (result, errorMsg) -> () in
}
}
}

Resources