how to wait for ui delegate in swift - ios

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
}
}
}

Related

How can we write Unit Test Case for the function in swift ios

I want to write a unit test case for the method which is one of the delegate methods in the view controller. I created a unit test case class for the VC and am trying to write a unit test for the method.
Here is the method which is implemented in VC. How can we write Unit Test Case?
extension DownloadBaseViewController:EMPDecisionTreeCoordinatorDelegate {
func decisionEmptyTreeFeedbackButtonTapped() {
if let feedbackNavVc = storyboard?.instantiateViewController(identifier: "PremiumFeedbackNavViewController") as? PremiumCustomNavigationController {
if let feedbackVc = feedbackNavVc.children.first as? PremiumFeedbackViewController {
feedbackVc.id = self.fileDetails?.id
self.decesiontreeCoordinator!.rootViewController.present(feedbackNavVc, animated: true, completion: nil)
}
}
}
}
Created a unit test class for VC and tried not able to write it properly followed few tutorials not found for delegate method.
import XCTest
class DownloadBaseViewControllerTests: XCTestCase {
var downloadBaseViewController: DownloadBaseViewController!
func testDecisionEmptyTreeFeedbackButtonTapped() throws {
let feedbackVCNavigation = downloadBaseViewController.decisionEmptyTreeFeedbackButtonTapped
XCTAssertNotNil(feedbackVCNavigation, "Download base view controller contains feedback view controller and succesfully able to navigate")
///Test case Build succeded but this is not the way to test it properly need heads up on this.
}
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
}
Refactor the DownloadBaseViewController in your app so you can mock the dependency:
extension DownloadBaseViewController:EMPDecisionTreeCoordinatorDelegate {
// Add this variable in DownloadBaseViewController
lazy var presentingController: ViewControllerPresenting? = self.decesiontreeCoordinator?.rootViewController
func decisionEmptyTreeFeedbackButtonTapped() {
if let feedbackNavVc = storyboard?.instantiateViewController(identifier: "PremiumFeedbackNavViewController") as? PremiumCustomNavigationController {
if let feedbackVc = feedbackNavVc.children.first as? PremiumFeedbackViewController {
feedbackVc.id = self.fileDetails?.id
self.presentingController?.present(feedbackNavVc, animated: true, completion: nil)
}
}
}
}
// You need this to mock the foreign dependency on UIViewController
protocol ViewControllerPresenting: AnyObject {
func present(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)?)
}
extension UIViewController: ViewControllerPresenting {}
In the tests you inject a Spy object that will help you validate the correct behaviour:
final class UIViewControllerSpy: ViewControllerPresenting {
var viewControllerToPresent: UIViewController!
func present(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)? = nil) {
self.viewControllerToPresent = viewControllerToPresent
}
}
class DownloadBaseViewControllerTests: XCTestCase {
var downloadBaseViewController: DownloadBaseViewController! = DownloadBaseViewController()
func testDecisionEmptyTreeFeedbackButtonTapped() throws {
// Given
let spyController = UIViewControllerSpy()
downloadBaseViewController.presentingController = spyController
// When
downloadBaseViewController.decisionEmptyTreeFeedbackButtonTapped()
// Then
let presentedController = spyController.viewControllerToPresent as? PremiumFeedbackViewController
XCTAssertNotNil(presentedController, "Download base view controller contains feedback view controller and succesfully able to navigate")
}
}

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.

Swift Extension with delegation (UIViewController)

I need ability to send email in a number of view controllers in my app. The code is same, take three params -- recipient address, body, and subject. If Mail is configured on device, initialize MFMailComposeViewController with the view controller as delegate. If Mail is not configured, throw an error. Also set current view controller as mailComposeDelegate to listen to callbacks. How does one use Swift extension to achieve it (setting delegate in extension being the main issue)?
I think you should create service Class for this type of Problem so it can be reused in other Application.
class MailSender : NSObject , MFMailComposeViewControllerDelegate {
var currentController : UIViewController!
var recipient : [String]!
var message : String!
var compltion : ((String)->())?
init(from Controller:UIViewController,recipint:[String],message:String) {
currentController = Controller
self.recipient = recipint
self.message = message
}
func sendMail() {
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients(recipient)
mail.setMessageBody(message, isHTML: true)
currentController.present(mail, animated: true)
} else {
if compltion != nil {
compltion!("error")
}
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
if compltion != nil {
compltion!("error")
}
controller.dismiss(animated: true)
}
}
now you can send mail From All three Controller using Following Code.
let mailsender = MailSender(from: self,recipint:["example#via.com"],message:"your message")
mailsender.sendMail()
mailsender.compltion = { [weak self] result in
print(result)
//other stuff
}
remember I have used simple Clouser(completion) that take String as Argument to inform whether it is success or fails but you can write as per your requirment.in addition you can also use delegate pattern instead of clouser or callback.
main advantage of this type of service Class is dependancy injection.for more details : https://medium.com/#JoyceMatos/dependency-injection-in-swift-87c748a167be
Create a global function:
func sendEmail(address: String, body: String, subject: String, viewController: UIViewController) {
//check if email is configured or throw error...
}

MessageComposeViewController not calling delegate

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?

MFMailComposeViewControllerDelegate not being called

I realize this question has been inexactly asked around, but I haven't been able to find an answer to my problem.
I have a UITableViewController with static cells. One of these cells is meant to open a Mail Composer view, and dismiss it through the delegate after the user sends or cancels the email. My problem is that the delegate method is not being called. Here is my code:
class SideMenuTableViewController: UITableViewController, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mailCVP.delegate = self
mailCVP = configureMailComposeVC()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailCVP, animated: true, completion: nil)
} else { //..// }
}
func configureMailComposeVC() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.setToRecipients(["momentosdetora#gmail.com"])
mailComposerVC.setSubject("Contacto Momentos de Tora")
return mailComposerVC
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
Can anybody spot anything I might be doing wrong?
Thanks.
MFMailComposeViewController is a subclass of UINavigationController, which already has a delegate property to handle navigation changes.
MFMailComposeViewController has another property called mailComposeDelegate, which is the property you are looking for.
Also, you should create the controller before setting the delegate.
Make sure you use
controller.mailComposeDelegate = self
Not this one
controller.delegate = self
mailCVP.delegate = self
mailCVP = configureMailComposeVC()
This code sets the delegate but then creates a new instance, which doesn't have a delegate...
Note that there is also no point in creating the VC instance if MFMailComposeViewController.canSendMail returns false.
First of all use
mailCVP.mailComposeDelegate = self
instead of
mailCVP.delegate = self
Moreover, in case of Swift 3, delegate method is somehow updated which is:
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?){
controller.dismiss(animated: true, completion: nil)
}

Resources