Unwind segue not dismissing view - swift

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.

Related

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

How to pop view controller and then show UIAlertController

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)

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

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()

How to unwind segue with alert view in Swift?

I have several View Controllers. I need to return to the first View Controller if the Alert View is confirmed. This is how I would do it without the unwind Segue:
#IBAction func unwindToDelete ( segue: UIStoryboardSegue ) {
let alertView = UIAlertController(title: "Delete?", message: "Are you sure you wante to delete?", preferredStyle: .ActionSheet)
let deleteAction = UIAlertAction (title: "Delete", style: .Destructive ) { alertAction in
self.deleteChoice = true
}
let cancelAction = UIAlertAction (title: "Cancel", style: .Cancel ) { alertAction in
}
alertView.addAction(deleteAction)
alertView.addAction(cancelAction)
self.presentViewController(alertView, animated: true, completion: nil)
}
But if I do that, in this code it crashes because of the last line of code.
This is the error:
2015-04-30 14:59:45.605 PhotosCollection[4624:182995]
popToViewController:transition: called on <UINavigationController 0x7a67aeb0>
while an existing transition or presentation is occurring; the navigation
stack will not be updated.
How can I complete the Alert View while being able to unwind segue.
Thank you
You are putting up the alert after the unwind has happened. You want to be able to not perform the unwind at all if the user chooses to cancel the delete. I would suggest the following:
Instead of wiring the unwind segue to your delete button, instead connect it to the view controller icon at the top of the view controller so that you can call the unwind programmatically. This answer shows you how to do that:
Setting up the unwind segue.
Give the unwind segue an identifier such as "doUnwind".
In the #IBAction for your delete button, put up the alert asking the user if they really want to delete.
In the handler for the delete button, call the unwind segue programmatically.
#IBAction func deleteButton (button: UIButton) {
let alertView = UIAlertController(title: "Delete?", message: "Are you sure you wante to delete?", preferredStyle: .ActionSheet)
let deleteAction = UIAlertAction (title: "Delete", style: .Destructive ) { alertAction in
self.performSegueWithIdentifier("doUnwind", sender: self)
}
let cancelAction = UIAlertAction (title: "Cancel", style: .Cancel ) { alertAction in
}
alertView.addAction(deleteAction)
alertView.addAction(cancelAction)
self.presentViewController(alertView, animated: true, completion: nil)
}