Delegates and protocols with VC not connected - swift

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

Related

How to dismiss MFMailComposeViewController from class mail?

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")
}
//...
}
}
}

How do I present a UIAlertController in SwiftUI?

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: "Ελέγξτε το δίκτυο σας και προσπαθήστε αργότερα.")
}
}

UITableView cell invokes UIAlertController popup ..."present" error

I have a tableview definition in which I am attempting to invoke an UIAlertController popup. I installed a button in the prototype tableView cell, when the button is touched, an IBAction handles the event. The problem is that the compiler won't let me.
present(alertController, animated: true, completion: nil)
Generates compiler error: "Use of unresolved identifier 'present'
Here is the code:
class allListsCell: UITableViewCell {
#IBOutlet var cellLable: UIView!
#IBOutlet var cellSelected: UILabel!
var colorIndex = Int()
#IBAction func cellMarkButton(_ sender: UIButton, forEvent event: UIEvent) {
if colors[self.colorIndex].selected == false {
colors[self.colorIndex].selected = true
cellSelected.text = "•"
let alertController = UIAlertController(title: "???", message: "alertA", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "dismiss", style: .default) { (action:UIAlertAction!) in
print("Sand: you have pressed the Dismiss button");
}
alertController.addAction(OKAction)
present(alertController, animated: true, completion: nil) // ERROR
} else {
colors[self.colorIndex].selected = false
cellSelected.text = ""
}
}
If I comment that one line, the app runs correctly for each cell...
You can't call present AlertController inside a tableView cell , it needs a subclass of UIViewController or other equivalent one , you should use a delegate or some sort of notification to handle that , see my answer here for the same problem AlertControllerCallInsideCell
Edit : Form Docs , it's an instance method inside UIViewController . so it can't be called inside any other class of other type (UITableViewCell) in your case
It is not possible to call the "present" method from a TableViewCell, I recommend having a function in the main controller to show your UIAlertController.
Using this code you can instantiate the parent driver and execute any available function:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
//UITableViewCell
if let controller = self.parentViewController as? YourController
{
controller.showAlert()
}
Here is an example of its use with a CollectionViewCell:
https://github.com/AngelH2/CollectionViewCell-Comunication/tree/master/CollectionCellAction

How to show an alert from another class in Swift?

I have a main class, AddFriendsController, that runs the following line of code:
ErrorReporting.showMessage("Error", msg: "Could not add student to storage.")
I then have this ErrorReporting.swift file:
import Foundation
class ErrorReporting {
func showMessage(title: String, msg: String) {
let alert = UIAlertController(title: title, message: msg, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
Obviously, self wouldn't work here, and is giving me an error. How can I refer to the currently open view controller (i.e. AddFriendsController in this circumstance), as I am wishing to use this same method in many different swift files?
Thanks.
You can create extension method for UIApplication (for example) which will return your topViewController:
extension UIApplication {
static func topViewController(base: UIViewController? = UIApplication.sharedApplication().delegate?.window??.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController, selected = tab.selectedViewController {
return topViewController(selected)
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}
And then your class will look like this:
class ErrorReporting {
static func showMessage(title: String, msg: String) {
let alert = UIAlertController(title: title, message: msg, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
UIApplication.topViewController()?.presentViewController(alert, animated: true, completion: nil)
}
}
Method need to be static to be able to call it as ErrorReporting.showMessage.
Actually, in my opinion the view controller presenting operation should be done on the UIViewController instance, not in a model class.
A simple workaround for it is to pass the UIViewController instance as a parameter
class ErrorReporting {
func showMessage(title: String, msg: String, `on` controller: UIViewController) {
let alert = UIAlertController(title: title, message: msg, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
controller.presentViewController(alert, animated: true, completion: nil)
}
}
And call it like below
ErrorReporting.showMessage("Error", msg: "Could not add student to storage.", on: self)
Swift 3 version of Maksym Musiienko's answer would be the following:
extension UIApplication {
static func topViewController(base: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return topViewController(base: selected)
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}

performSegueWithIdentifier causes crash

I'm setting up a sign up/login page using framework PARSE on XCode 6.
When I try to perform a segue (it is spelled correcty), hover, the app crash, even though the segue is inside an if statement.
Here's the code:
import UIKit
import Parse
class ViewController: UIViewController, UINavigationControllerDelegate{
var signUpMode = false
func displayAlert(title:String, message:String){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
//Outlet and actions
#IBOutlet var username: customTextField!
#IBOutlet var email: customTextField!
#IBOutlet var password: customTextField!
//Need the outlets for changes betweeen signUp and logIn modes!!!
#IBAction func signUp(sender: AnyObject) {
if signUpMode == true {
var user = PFUser()
user.username = username.text
user.password = password.text
user.email = email.text
// other fields can be set just like with PFObject
//user["phone"] = "415-392-0202"
user.signUpInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
// Hooray! Let them use the app now.
} else {
println("error")
self.displayAlert("Username already in use", message: "Please use another username")
}
}
}
else {
PFUser.logInWithUsernameInBackground(email.text, password:password.text) {
(user: PFUser!, error: NSError!) -> Void in
if user != nil {
self.displayAlert("You're in", message: "And you'll be successful")
self.performSegueWithIdentifier("goToPost", sender: self)
} else {
self.displayAlert("Wrong username or password", message: "Please try again")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
if signUpMode == false {
self.username.hidden = true
self.email.placeholder = "username"
}
}
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
performSegueWithIdentifier("goToPost", sender: self)
}
}
}
The segue is inside the viewWillAppear method.
PFUser().currentUser() stores information about the current logged in user, so it's nil if no user is logged in.
Can you find out why it crashes?
I tried to put the segue inside viewDidLoad, but nothing else, it didn't even crashed.
Try segueing in viewDidAppear: and check if your segue identifier matches the one on your storyboard.