I have separated the mail functions from my UIviewController and placed them into a class ‘Mail’. Works fine, but now I do have trouble to dismiss my ‘MFMailComposeViewController’. The delegate ‘mailComposeController’ is not called, Any ideas how to fix?
import Foundation
import MessageUI
class Mail: UIViewController, MFMailComposeViewControllerDelegate{
static func createMail2(
fromViewController:UIViewController,
recipients :String,
messageTitle:String,
messageText :String,
attachment :AnyObject?)
{
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
//mail.mailComposeDelegate = self //Cannot assign value of type 'Mail.Type' to type 'MFMailComposeViewControllerDelegate?'
mail.mailComposeDelegate? = fromViewController as! MFMailComposeViewControllerDelegate //added ? (optional)
mail.setToRecipients([recipients]) //(["mail#to.me"])
mail.setSubject(messageTitle) //("your subject text")
mail.setMessageBody(messageText, isHTML: false)
//ggf. Attachment beifügen>>>
if attachment != nil {
//attachment vorhanden, also anhängen
let attachName = "\(messageTitle).pdf"
mail.addAttachmentData(
attachment as! Data,
mimeType: "application/octet-stream", //für binäre Daten, funktioniert immer
fileName: attachName)
}//end if attachment
//<<<ggf. Attachment beifügen
// Present the view controller modally
fromViewController.present(mail, animated: true) //show mail
} else {
// show failure alert
print("Mail services are not available")
let alert = UIAlertController(title: "Mail error", message: "Your device has not been configured to send e-mails", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
fromViewController.present(alert,animated: true, completion: nil)
}//end if else
}//end func createMail
//mail delegate
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
//It’s called when the user dismisses the controller, either by sending the email or canceling the action. Either way, you have to dismiss the controller manually.
//ggf. noch aktionen...
controller.dismiss(animated: true) //remove the mail view
}//end func mailComposeController
}//end class Mail
What you need is to create a static shared instance of your Mail controller and set if as the mailComposeDelegate. You should also create a controller property in your mail controller to keep a reference of the view controller that has invoked the mail composer and declare your createMail as an instance method (not static):
import UIKit
import MessageUI
class MailComposer: NSObject, MFMailComposeViewControllerDelegate {
static let shared = MailComposer()
private var controller: UIViewController?
func compose(controller: UIViewController, recipients: String,
messageTitle: String, messageText: String, fileURL: URL? = nil) { //, completion: #escaping ((MFMailComposeResult, Error?) -> Void)) {
self.controller = controller
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = MailComposer.shared
mail.setToRecipients([recipients])
mail.setSubject(messageTitle)
mail.setMessageBody(messageText, isHTML: false)
if let fileURL = fileURL {
do {
try mail.addAttachmentData(Data(contentsOf: fileURL), mimeType: "application/octet-stream", fileName: fileURL.lastPathComponent)
} catch {
print(error)
}
}
controller.present(mail, animated: true)
} else {
let alertController = UIAlertController(title: "Mail Compose Error",
message: "Your device has not been configured to send e-mails",
preferredStyle: .alert)
alertController.addAction(.init(title: "OK", style: .default) { _ in
alertController.dismiss(animated: true)
})
controller.present(alertController, animated: true)
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
// You should switch the result only after dismissing the controller
controller.dismiss(animated: true) {
let message: String
switch result {
case .sent: message = "Message successfully sent!"
case .saved: message = "Message saved!"
case .cancelled: message = "Message Canceled!"
case .failed: message = "Unknown Error!"
#unknown default: fatalError()
}
let alertController = UIAlertController(title: "Mail Composer",
message: message,
preferredStyle: .alert)
alertController.addAction(.init(title: "OK", style: .default) { _ in
alertController.dismiss(animated: true)
})
self.controller?.present(alertController, animated: true) {
self.controller = nil
}
}
}
}
Usage:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let recipients = "user#email.com"
let messageTitle = "New Message"
let messageText = "Mail Test"
MailComposer.shared.compose(controller: self, recipients: recipients, messageTitle: messageTitle, messageText: messageText)
}
}
The UIViewController that you're passing as the parameter for fromViewController should conform to MFMailComposeViewControllerDelegate protocol and you should be implementing mailComposeController(_: didFinishWith: in the definition of that controller.
class Mail: UIViewController {
static func createMail2(
fromViewController: UIViewController,
recipients :String,
messageTitle:String,
messageText :String,
attachment :AnyObject?) {
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
if let fromViewController = fromViewController as? MFMailComposeViewControllerDelegate {
mail.mailComposeDelegate = fromViewController
} else {
print("fromViewController needs to conform to MFMailComposeViewControllerDelegate")
}
//...
}
}
}
Related
I've been trying to present a UIAlertController when user entered wrong password for their account, the UIAlertController is located in one separate file inside a Model group which I extend the UIViewController class to add this alert functionality to it. I also has another file inside my model group namely LogIn which I wrote all the logic behind the login process so that I can call it to my LogInVC. However, I got an error of "Attempt to present on whose view is not in the window hierarchy!" whenever the function get call inside my LogInVC. I'm trying to make my project in MVC and I know what caused this error but I just don't know how to fix it. May anyone tell me how to fix this problem?
Alert
import Foundation
import UIKit
extension UIViewController {
//MARK: - Not Enough Information Alert
func notEnoughInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
//MARK: - Incorrect Username and Password
func wrongInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "Try again", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
LogIn
import Foundation
import Firebase
class LogIn: UIViewController{
let db = Firestore.firestore()
//MARK: - userValidation()
func userValidation(Username:String, Password:String){
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapShotDocument = querySnapshot?.documents {
for doc in snapShotDocument {
let data = doc.data()
if let username = data[C.username] as? String, let password = data[C.password] as? String {
if Username == username, Password == password {
print("Log in Successfully")
}
else {
self.wrongInfo(title: "Incorrect password", message: "Try again please")
}
}
}
}
}
}
}
}
LogInVC
import UIKit
import Firebase
class LogInVC: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var logInBtn: UIButton!
let db = Firestore.firestore()
let logIn = LogIn()
override func viewDidLoad() {
super.viewDidLoad()
//logInBtn.layer.cornerRadius = logInBtn.frame.height/5
}
#IBAction func logInBtn(_ sender: UIButton) {
if let username = emailTextField.text, let password = passwordTextField.text{
if username.isEmpty || password.isEmpty{
notEnoughInfo(title: "Not enough information", message: "Please fill in all the necessary information.")
}else{
logIn.userValidation(Username: username, Password: password) //here is where problem occured
//move to another viewcontroller
}
}
}
#IBAction func signUpBtn(_ sender: UIButton) {
let push = storyboard?.instantiateViewController(withIdentifier: C.signUpVC) as! SignUpVC
push.modalPresentationStyle = .fullScreen
present(push, animated: true, completion: nil)
}
} //ends of class
You need to first dismiss the current present alert or present controller. currently you are trying to present controller over a controller that's why it shows this error. Don't present . remove this line from self.wrongInfo(title: "Incorrect password", message: "Try again please") from LogIn.
try this and you can comment again if there is anything regarding this.
I have a parent view controller i.e. HomeViewController which has a navigation bar button via which a user can trigger an alert and enter a string. This string needs to be passed to the child view controller.
Here is the relevant code in the parent view controller:
protocol NewSectionDelegate {
func sendSectionName(name : String)
}
class HomeViewController: UIViewController {
var sectionNameDelegate : NewSectionDelegate?
func addCardAsChild() { // add the child VC to the parent VC
if cardViewController == nil {
cardViewController = CardViewController()
addViewController(newViewController: cardViewController!)
} else {
addViewController(newViewController: cardViewController!)
}
}
func triggerAlert() {
let alertController = UIAlertController(title: "New section", message: "Name the section with a words or a sentence", preferredStyle: .alert)
alertController.addTextField(configurationHandler:
{(_ textField: UITextField) -> Void in //txtview customization
})
let addAction = UIAlertAction(title: "Add", style: .default) { _ in
guard let sectionName = alertController.textFields?.first?.text else { return }
self.sectionNameDelegate?.sendSectionName(name: sectionName) // Sending string; verified that the string is not nil
}
alertController.addAction(addAction)
self.present(alertController, animated: true, completion: nil)
}
And here is the child view controller:
class CardViewController: UIViewController, NewSectionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let homeViewController = HomeViewController()
homeViewController.sectionNameDelegate = self
}
func sendSectionName(name: String) {
print("received name:\(name)") // This line of code is never called
}
The data is not getting passed and I have no idea why.
Is this what you are looking for?
protocol NewSectionDelegate {
func sendSectionName(name : String)
}
class HomeViewController: UIViewController {
var sectionNameDelegate : NewSectionDelegate?
var cardViewController = CardViewController()
func addCardAsChild() { // add the child VC to the parent VC
self.addChild(self.cardViewController)
}
func triggerAlert() {
let alertController = UIAlertController(title: "New section", message: "Name the section with a words or a sentence", preferredStyle: .alert)
alertController.addTextField(configurationHandler:
{(_ textField: UITextField) -> Void in //txtview customization
})
let addAction = UIAlertAction(title: "Add", style: .default) { _ in
guard let sectionName = alertController.textFields?.first?.text else { return }
self.sectionNameDelegate?.sendSectionName(name: sectionName) // Sending string; verified that the string is not nil
}
alertController.addAction(addAction)
self.present(alertController, animated: true, completion: nil)
}
}
class CardViewController: UIViewController, NewSectionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
guard let homeViewController = self.parent as? HomeViewController else { return }
homeViewController.sectionNameDelegate = self
}
func sendSectionName(name: String) {
print("received name:\(name)") // This line of code is never called
}
}
I think the delegate method is getting called but for the internal HomeViewController you're making in the ChildViewController viewDidLoad method. It looks like you're expecting the method to get called on a different object. I would remove that code from viewDidLoad and set the sectionNameDelegate in HomeViewController
In UIKit, it is common to present UIAlertController for modal pop up alert messages in response to some action.
Is there a modal alert controller type in SwiftUI?
Is there a way to present a UIAlertController from SwiftUI classes? It seems like this may be possible using UIViewControllerRepresentable but not sure if that is required?
This just works:
class func alertMessage(title: String, message: String) {
let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { (action: UIAlertAction) in
}
alertVC.addAction(okAction)
let viewController = UIApplication.shared.windows.first!.rootViewController!
viewController.present(alertVC, animated: true, completion: nil)
}
Put it in a Helper-Class.
Usage:
Helper.alertMessage(title: "Test-Title", message: "It works - even in SwiftUI")
Use Alert instead.
import SwiftUI
struct SwiftUIView: View {
#State private var showAlert = false;
var body: some View {
Button(action: { self.showAlert = true }) {
Text("Show alert")
}.alert(
isPresented: $showAlert,
content: { Alert(title: Text("Hello world")) }
)
}
}
Bind to isPresented in order to control the presentation.
I'm using extension of UIViewController to get current vc and UIAlertController to present 'Alert'. Maybe you can try this as followings:
extension for UIViewController
extension UIViewController {
class func getCurrentVC() -> UIViewController? {
var result: UIViewController?
var window = UIApplication.shared.windows.first { $0.isKeyWindow }
if window?.windowLevel != UIWindow.Level.normal {
let windows = UIApplication.shared.windows
for tmpWin in windows {
if tmpWin.windowLevel == UIWindow.Level.normal {
window = tmpWin
break
}
}
}
let fromView = window?.subviews[0]
if let nextRespnder = fromView?.next {
if nextRespnder.isKind(of: UIViewController.self) {
result = nextRespnder as? UIViewController
result?.navigationController?.pushViewController(result!, animated: false)
} else {
result = window?.rootViewController
}
}
return result
}
}
extension for UIAlertController
extension UIAlertController {
//Setting our Alert ViewController, presenting it.
func presentAlert() {
ViewController.getCurrentVC()?.present(self, animated: true, completion: nil)
}
func dismissAlert() {
ViewController.getCurrentVC()?.dismiss(animated: true, completion: nil)
}
}
And you can create your showAlert function now
func showMyAlert() {
let myAlert = UIAlertController(title: "Confirm order", message: "Are you sure to order two box of chocolate?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok!", style: .default) { (_) in
print("You just confirm your order")
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in
print(You cancel it already!)
}
myAlert.addAction(okAction)
myAlert.addAction(cancelAction)
myAlert.presentAlert()
}
Wish my answer can help you. :-)
You can present UIKit Alert in SwiftUI using notificationCenter
At SceneDelegate.swift on "func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)" insert this code {
NotificationCenter.default.addObserver(self, selector: #selector(self.showAlert), name: Notification.Name("showAlert"), object: nil)
and add this function
#objc private func showAlert(notification: NSNotification){
let msg: String = notification.object as! String
let alert = UIAlertController(title: "Title", message: msg, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "οκ", style: .cancel) { (action) in
}
alert.addAction(cancelAction)
DispatchQueue.main.async {
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
Now you can make the app to appear a message with UIKit AlertController writing this code on an action in a swifui class
var body: some View {
Button(action:{
NotificationCenter.default.post(name: Notification.Name("showAlert"), object: "Ελέγξτε το δίκτυο σας και προσπαθήστε αργότερα.")
}
}
I have used side navigation menu(SWReveal). I have 4 ViewControllers. How can use same alertAction in different views.
You can create UIViewController extension like below:
extension UIViewController {
func showAlert(title: String?, message: String?, actionTitles:[String?], actions:[((UIAlertAction) -> Void)?]) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: .default, handler: actions[index])
alert.addAction(action)
}
self.present(alert, animated: true, completion: nil)
}
}
And you can use this alert in UIViewController like below:
showAlert(title: "Your Title", message: "Your custom Message", actionTitles: ["Ok","Cancel"], actions: [{ action1 in
//OK Action
}, { action2 in
// Cancel Action
}
])
Hope will get your solution.
You can also use like this way.
class IOSPublicDefaultAlert: NSObject{
var viewController: UIViewController?
var actionCompletion: ((String) -> ())?
var alertTitle: String?
var alertMessage : String?
var alertType: UIAlertControllerStyle?
var actionTitleAndType: [String: UIAlertActionStyle]?
init(viewController : UIViewController,alertTitle: String?,alertMessage : String?,alertType: UIAlertControllerStyle = .alert,actionTitleAndType: [String: UIAlertActionStyle] ,actionCompletion : ((String)->())?){
super.init()
self.viewController = viewController
self.actionCompletion = actionCompletion
self.alertTitle = alertTitle
self.alertMessage = alertMessage
self.alertType = alertType
self.actionTitleAndType = actionTitleAndType
showAlert()
}
func showAlert(){
let alert = UIAlertController.init(title: alertTitle, message: alertMessage, preferredStyle: self.alertType ?? .alert)
for (actionTitle, actionType) in actionTitleAndType!{
let action = UIAlertAction(title: actionTitle, style: actionType) { (action) in
if let com = self.actionCompletion{
com(actionTitle)
}
}
alert.addAction(action)
}
viewController?.present(alert, animated: true, completion: nil)
}
}
and use add where you like as below sample
_ = IOSPublicDefaultAlert.init(viewController: self, alertTitle: "Warning!!!", alertMessage: alertMessage, actionTitleAndType: ["Ok" : .destructive, "Cancel" : .default], actionCompletion: { [unowned self] (title) in
if title == "Ok"{
}
})
In swift, your project, you can create a new .swift file and in this file create a class:
import UIKit
import Foundation
class yourFileName {
//Create a class function alerview
class func displayAlert(title: String, withMessage msg: String, andbtnTitle btntitle: String, in vc: UIViewController) {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: btntitle, style: UIAlertActionStyle.default, handler: nil))
appDelegate.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
//and now your any ViewController.swift file or any other file in your project you can access alert following way.
class viewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
yourfilename.displayAlert(title: "Alert", withMessage msg: "my alert view display", andbtnTitle btntitle: "Ok", in vc: self) // access your alertview
}
}
I hope it's work for you.
Create BaseController with a method that can show alert.
//Copyright © 2017 dip. All rights reserved.
import UIKit
class BaseController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
///This is common method to show alert with same action
func showAlert() {
let alert = UIAlertController(title: "Alert", message: "my msg on alert", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
///This will be common alert ok aciton for all child controllers.
print("Do some userful common work..")
}))
self.present(alert, animated: true, completion: nil)
}
}
Inherit Your 4 controllers from BaseController
// Copyright © 2017 dip. All rights reserved.
//
import UIKit
class ChildVC: BaseController {
override func viewDidLoad() {
super.viewDidLoad()
//call show alert when ever you wish
///This method will call showAlert() method on super class (BaseController)
self.showAlert()
}
}
Call self.showAlert() method from child when you want show alert with common action.
// MARK: - Alertable View
protocol AlertableView {
// Use handler if need catch cancel alert action
typealias CompletionHandler = (() -> Void)
func displayAlert(with title: String, message: String, actions: [UIAlertAction]?)
func displayAlert(with title: String, message: String, style: UIAlertControllerStyle, actions: [UIAlertAction]?, completion: CompletionHandler?)
}
extension AlertableView where Self: UIViewController {
func displayAlert(with title: String, message: String, actions: [UIAlertAction]?) {
self.displayAlert(with: title, message: message, style: .alert, actions: actions, completion: nil)
}
func displayAlert(with title: String, message: String, style: UIAlertControllerStyle, actions: [UIAlertAction]?, completion: CompletionHandler?) {
let alertCancelAction = UIAlertAction(title: "Cancel".localized, style: .cancel) { (action) in
guard let completion = completion else { return }
completion()
}
let alertController = UIAlertController(title: title, message: message, preferredStyle: style)
if let actions = actions {
for action in actions {
alertController.addAction(action)
}
alertController.addAction(alertCancelAction)
} else {
// If not any custom actions, we add OK alert button
let alertOkAction = UIAlertAction(title: "OK".localized, style: .cancel) { (action) in
guard let completion = completion else { return }
completion()
}
alertController.addAction(alertOkAction)
}
self.present(alertController, animated: true, completion: nil)
}
}
Create a common function ,
import UIKit
class AlertClass: NSObject {
func showAlertWithVC(_ VC : UIViewController, andMessage message: String ){
DispatchQueue.main.async {
let alert = UIAlertController(title: "APPLICATION_NAME", message: message , preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
VC.present(alert, animated: true, completion: nil)
}
}
}
Simply call AlertClass().showAlertWithVC() where you want to show Alert.
I am trying to add a send email button to a Sprite Kit game. I can get the email dialog to show up. But if I hit cancel, the app will crash or do nothing. If I hit send, the email will send, but the dialog stays. I cannot get the mailComposeController function to fire...please help!
Code:
import Foundation
import UIKit
import MessageUI
class MailViewController: UIViewController, MFMailComposeViewControllerDelegate {
let systemVersion = UIDevice.currentDevice().systemVersion
let devicemodel = UIDevice.currentDevice().model
let appVersion = NSBundle.mainBundle().infoDictionary?["CFBundleShortVersionString"] as! String
let appBuild = NSBundle.mainBundle().infoDictionary?["CFBundleVersion"] as! String
let myrootview2 = UIApplication.sharedApplication().keyWindow?.rootViewController
let mailComposerVC = MFMailComposeViewController()
override func viewDidLoad() {
super.viewDidLoad()
}
func sendEmailButtonTapped(sender: AnyObject) {
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.view.window?.rootViewController = mailComposerVC
print("This is the rootview2: \(myrootview2)")
myrootview2!.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
var msgbody: String
mailComposerVC.mailComposeDelegate = self
msgbody = "\n\nDevice: \(devicemodel)\niOS Version: \(systemVersion)\nApp Version: \(appVersion)\nApp Build Number: \(appBuild)\n"
mailComposerVC.setToRecipients(["test1#test.com"])
mailComposerVC.setSubject("test subject")
mailComposerVC.setMessageBody(msgbody, isHTML: false)
//print(mailComposerVC)
return mailComposerVC
}
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// THIS DOESN'T GET CALLED WHEN SENDING OR CANCELLING EMAIL!
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
let test1 = result.rawValue
print(test1)
print(controller)
print(self)
print(myrootview2)
}
The issue is you are making the mailVC as the root view, you have to present it on your view like given below
#IBAction func sendEmailButtonTapped(sender: AnyObject) {
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
controller.dismissViewControllerAnimated(true, completion: nil)
}