How to pop view controller and then show UIAlertController - swift

Say there is an error in ViewController2, and I want to go back to ViewController1, the previous view controller and then display an alert. Right now if I put this in ViewController2
#IBAction func testing(_ sender: Any) {
navigationController?.popViewController(animated: true)
Alert.alert(userTitle: "Error", userMessage: " ", userOptions: " ", in: LandingViewController() as UIViewController)
}
using this as the Alert class
public class Alert {
class func alert(userTitle: String?, userMessage: String, userOptions: String, in vc: UIViewController) {
DispatchQueue.main.async {
let alert = UIAlertController(title: userTitle, message: userMessage, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: userOptions, style: UIAlertActionStyle.default, handler: nil))
vc.present(alert, animated: true, completion: nil)
}
}
}
It will throw an error
So is there a way to do this?

The problem is that LandingViewController() as UIViewController is not rendered in UiWindow
try this
guard let nav = navigationController, let top = nav.topViewController else {
return
}
nav.popViewController(animated: true)
Alert.alert(userTitle: "Error", userMessage: " ", userOptions: " ", in: top)

Related

Unwind segue not dismissing view

The Issue:
Unwind segue not dismissing view, even though it works.
Scenario:
I have an "add product" screen, and when the product is added, I fire up unwind segue, which in turn shows a popup: "New Product added".
The Facts:
1) Product is successfully added to Database
2) the popup "Product added" is shown in the #IBAction of the unwind segue in target, and it appears
3) The popup "Product Added" is shown over the AddProductScreen view
Picture 1: Existence of Unwind segue
#IBAction located in target view:
#IBAction func unwindFromAddProductViewSuccess(segue: UIStoryboardSegue)
{
AlertManager.showTimedAlert(title: "Success", message: "New Product added", timeShowing: 1, callingUIViewController: self)
}
Function being called in AddProductView for registering the product:
private func registerProductAndPerformSegue(productToRegister prod: Product)
{
self.registerProduct(prodToRegister: prod)
Constants.logger.debug("New product registered")
self.performSegue(withIdentifier: "unwind_from_add_product_success", sender: self)
}
In the "Add Product View", after "Confirm" is clicked, an alert shows with "Are you sure?" and then you are prompt to click "Yes" or "No".
This is the code for the alert:
AlertManager.ShowAlert(controllerToShowAlert: self, alertTitle: LocalizationStrings.Products.ADD_PRODUCT_TITLE, alertText: LocalizationStrings.Products.ADD_PRODUCT_CONFIRMATION,
alertStyle: UIAlertControllerStyle.alert,
leftButtonAction: UIAlertAction(title: LocalizationStrings.YES_TEXT, style: .default, handler:
{
(action: UIAlertAction!) in
let user = Auth.auth().currentUser
if let user = user
{
let product : Product = Product(name: name!, price: Double(price!)!, currency: "USD", description: desc!,
location: "USA", ownerID: user.uid, ownerName: user.displayName!, uniqueID: "", mainImageURL: nil, category: category)
let storageRef = Storage.storage().reference().child(product.getUniqueID()).child("pic0.jpg")
if let mainChosenImage = self.selectedImageToUpload
{
Constants.logger.debug("Product picture chosen")
if let uploadData = UIImageJPEGRepresentation(mainChosenImage, 0.2)
{
storageRef.putData(uploadData, metadata: nil)
{
(StorageMetaData, error) in
if error != nil
{
Constants.logger.error("Add Product: Couldn't store image in database!")
return
}
self.mainImageURL = StorageMetaData?.downloadURL()?.absoluteString
if let urlString = self.mainImageURL
{
product.AddImageURLToProduct(URL: urlString)
self.registerProductAndPerformSegue(productToRegister: product)
}
}
}
else
{
Constants.logger.error("Couldn't convert uploaded image to UIImageJPEGRepresentation")
}
}
else
{
Constants.logger.debug("Product picture NOT chosen")
self.registerProductAndPerformSegue(productToRegister: product)
}
}
}),
rightButtonAction: UIAlertAction(title: LocalizationStrings.NO_TEXT, style: .cancel, handler: { (action: UIAlertAction!) in
print("Handle Cancel Logic here")
}))
What am I doing wrong here?
Without more details about your Storyboard setup and your code, it's hard to tell what the exact problem is.
But I'll assume that you have two view controllers and I will try to guide you through the steps to do an unwindSegue and you can check if you maybe have missed something.
Assuming that you have two view controllers: FirstViewController andAddProductViewController, andAddProductViewController` is presented using a push segue.
Inside FirstViewController you have:
#IBAction func unwindFromAddProductViewSuccess(segue: UIStoryboardSegue) {
AlertManager.showTimedAlert(title: "Success", message: "New Product added", timeShowing: 1, callingUIViewController: self)
}
Then, You connect your "save" or "add" button from AddProductViewController to unwindFromAddProductViewSuccess: action method.
You can also connect your "save" or "add" button to SecondViewController to debug that the segue is being called:
class SecondViewController: UIViewController {
#IBOutlet weak var saveButton: UIBarButtonItem!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let uiBarButtonItem = sender as? UIBarButtonItem else {
print("There is no UIBarButtonItem sender")
return
}
if saveButton == uiBarButtonItem {
print("save button tapped")
}
print("A segue with an identifier \(segue.identifier ?? "") was called.")
}
}
Assuming that you don' have a bar button item and want to call unwidnSegue manually, maybe after the user taps on an UIAlertController action, then you can do something like this:
#IBAction func orderButtonTapped(_ sender: UIButton) {
let alert = UIAlertController(title: "Order Placed!", message: "Thank you for your order.\nWe'll ship it to you soon!", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {
(_)in
self.performSegue(withIdentifier: "unwindToMenu", sender: self)
})
alert.addAction(OKAction)
self.present(alert, animated: true, completion: nil)
}
There are also some cases when the UIViewController that you are trying to unwind from is at the bottom of the UINavigationController stack, in this case, calling unwindSegue will not do anything. There was a similar question related to that before, here.
I hope that this info can help you figure out the problem.

UIAlertController pushes navigation bar item

UIAlewrtController pushes the button in my navigation controller down (see image)
Sequence of execution:
on push + the alert controller is activated
on selecting "Take an Action" or "Request an Action" the view controller ActionDetialsVC is activated.
on showing ActionDetialsVC
on back the ActionDetialsVC is closed
The iitial controlers BACK arrow moved
When putting breakpoints on:
self.present (alert, animated: true, completion: nil)
the first Swift instruction in the 2 alert handlers
The strange navigation back button behavior is visible between these breakpoints.
The issue ONLY occurs when introducing Alert Controllers in my views !!!!!
Thank you for your help
Image of the issue:
The code:
func addPressed(_ sender: Any) {
let alert = UIAlertController (title: "Add an Action", message: nil, preferredStyle: UIAlertControllerStyle.alert)
let request = UIAlertAction (title: actionType.Request.Str(), style: UIAlertActionStyle.default, handler: {
action in
self.actionTypeTemp = actionType.Request.Str()
self.performSegue(withIdentifier: "segueActionDetailsVC", sender: nil)
} )
let take = UIAlertAction (title: actionType.Take.Str(), style: UIAlertActionStyle.default, handler: {
action in
self.actionTypeTemp = actionType.Take.Str()
self.performSegue(withIdentifier: "segueActionDetailsVC", sender: nil)
} )
let done = UIAlertAction (title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil )
alert.addAction(request)
alert.addAction(take)
alert.addAction(done)
self.present (alert, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueActionDetailsVC" {
if let destination = segue.destination as? ActionDetailsVC {
if let action = sender as? PActions {
destination.actionToEdit = action
}
else {
if actionTypeTemp == actionType.Request.Str() {
destination.relatedContact = self.contact
destination.title = actionType.Request.Str()
}
else {
destination.relatedContact = self.contact
destination.title = actionType.Take.Str()
}
}
}
}
}

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

Why can't won't the MFMailComposerViewController be dismissed?

Whenever I press "Cancel" then "Delete Draft", the mail composer won't be dismissed. The error I'm getting is "Thread 1: EXC_BAD_ACCESS (code=1, address=0x40363380)"
In my TableViewController I have:
#IBAction func mailButton(sender: AnyObject) {
let emailComposer = EmailComposer()
if email != "" {
print(email)
if emailComposer.canSendMail() {
emailComposer.setRecipient(email)
let configuredMailComposeViewController = emailComposer.configuredMailComposeViewController()
presentViewController(configuredMailComposeViewController, animated: true, completion: nil)
}
} else {
let alertController = UIAlertController(title: "Sorry!", message: "No email found for this contact", preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
//do nothing
}))
self.presentViewController(alertController, animated: true, completion:nil)
}
}
For those who don't know, EXC_BAD_ACCESS means its trying to access something in memory that is no longer there. I wrongfully created the EmailComposer() object after the button tap so it was going out of scope. So this:
let emailComposer = EmailComposer()
...should have been created here, for example:
class TableViewController: UITableViewController {
let emailComposer = EmailComposer()

UIAlertController whose view is not in the window hierarchy

I'm trying to create an app with Swift and Parse. I'm using Xcode 7 and Swift 2.
I want to show a alert message when User is login failed, here is my function:
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
But I've got this error when run in the emulator:
2015-10-02 11:32:39.988 RedString[2089:886501] Warning: Attempt to present <UIAlertController: 0x7a934400> on <MyProject.ViewController: 0x7b985150> whose view is not in the window hierarchy!
Login failed.........!
I've googled about it, but I didn't found the clear solution.
Here is my whole class:
import UIKit
import Parse
import ParseUI
class ViewController: UIViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
self.setupLoginView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser){
self.dismissViewControllerAnimated(true, completion: nil)
}
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser){
self.dismissViewControllerAnimated(true, completion: nil)
}
func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?){
print("Sign up failed.........!")
}
func setupLoginView(){
if(PFUser.currentUser() == nil){
let loginViewController = PFLogInViewController()
loginViewController.delegate = self
let signUpViewController = PFSignUpViewController()
signUpViewController.delegate = self
loginViewController.logInView!.logo = UIImageView(image: UIImage(named:"logo.png"))
loginViewController.signUpController = signUpViewController
self.presentViewController(loginViewController, animated: true, completion: nil)
}else{
print("login as: " + PFUser.currentUser()!.username!)
//prepare new view here
}
}
}
when
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
is called, your view controller is not on screen. Therefore you could either
dismiss the logInViewController, then display the alert and pass
setUpLogInView as the handler.
or in the above function, try logInController.presentViewController
instead of self.presentViewController
You have to call presentViewController:animated:completion on your login view controller.
Here is a simplified version of your class to show you what I mean:
class ViewController: UIViewController {
weak var loginViewController: UIViewController?
func setupLoginViewController() {
let loginVC = PFLogInViewController()
// setup loginVC
loginViewController = loginVC // store the reference
}
func loginDidFail() {
let alertVC = UIAlertController(...)
// setup alertVC
loginViewController?.presentViewController(...) // present the alert from the login view controller
}
}
I had the same problem in Objective-C, it occurs when the parent window has not yet been drawn to the screen. I solved the problem by calling the code from within viewDidAppear rather than viewDidLoad. See UIAlert error, whose view is not in the window hierarchy