I am trying to present an alert to the user if they need to be logged in to access a feature but when I press the login button in the alert self.self.performSegueWithIdentifier("tryonToLogin", sender:self) does not appear to do anything.
LoginViewController
#IBAction func unwindToLogin(segue:UIStoryboardSegue){
}
UIAlert
#IBAction func goToGallery(sender: UIButton){
if(isLoggedIn){
performSegueWithIdentifier("ShowGallery", sender: sender)
}else{
showMessage("You must login to view access this feature", sender:sender)
}
}
func showMessage(popUpMessageText : String, sender:UIButton){
let messageTitle = "Error"
print("prepreform segue")
performSegueWithIdentifier("unwindToLogin", sender:sender)
let refreshAlert = UIAlertController(title: messageTitle, message: popUpMessageText, preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction( UIAlertAction(title: "Login", style: .Default, handler: { (action: UIAlertAction!) in
print("Handle Login redirect logic here")
self.performSegueWithIdentifier("unwindToLogin", sender: self)
print("after Handle Login redirect logic here")
}))
refreshAlert.addAction(UIAlertAction(title: "Cancel", style: .Default, handler: { (action: UIAlertAction!) in
print("Handle Cancel logic here")
}))
presentViewController(refreshAlert, animated: true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("prepare for segue called")
self.camera = nil
}
There are no errors. it is getting called as if ii put in a id that does not exist i get the no segue with specified identifier found error message.
Does the UIAlert affect the calling of the performSegueWithIdentifier()
The following post uses this approach so I think it should work. what am i missing.
**IB Screenshots **
Edit
I have tried moving self.performSegueWithIdentifier("unwindToLogin", sender:self) to view did appear and it still is snot working.
The self inside the block is the UIAlertController, not the UIViewController. Create a weak reference to the UIViewController, and call the method on that object.
weak var weakSelf = self
let refreshAlert = UIAlertController(title: messageTitle, message: popUpMessageText, preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction( UIAlertAction(title: "Login", style: .Default, handler: { (action: UIAlertAction!) in
print("Handle Login redirect logic here")
dispatch_async(dispatch_get_main_queue()) {
weakSelf?.performSegueWithIdentifier("unwindToLogin", sender:self)
}
}))
Seeing that you have not provide sufficient informations, I present you this 'solution'.
Create an extension of UIViewController in order to implement facility function to implement an alertView-
I suppose that you put your code on "viewDidLoad": you have to move code on "viewDidAppear".
please check what operation you do in prepareForSegue
I think this will be sufficient.
extension UIViewController {
/**
Show alert view with OK/Cancel button.
- parameter title: alert title
- parameter message: alert message
- parameter onOk: callback on ok button tapped
- parameter onCancel: callback on cancel button tapped
- returns: alert controller
*/
func showAlert(title title: String, message: String, onOk: (() -> ())?, onCancel: (() -> ())? = nil) -> UIAlertController {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "Ok", style: .Default) { (alert: UIAlertAction!) -> Void in
onOk?()
}
let cancelAction = UIAlertAction(title: "Cancel"), style: .Default) { (alert: UIAlertAction!) -> Void in
onCancel?()
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.presentViewController(alertController, animated: true, completion: nil)
return alertController
}
}
class LoginViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let title = "Login"
let message = messageTitle
self.showAlert(title: title, message: message, onOk: { () -> () in
self.performSegueWithIdentifier("unwindToLogin", sender: self)
})
}
}
Related
I want to have a base UIAlertController and I want to use it in different classes by just passing the buttons and their closures. To achieve this, I created an extension from UIAlertController like below:
extension UIAlertController {
func showAlert(buttons: [ButtonsAction]?) -> UIAlertController {
let alert = self
guard let alertButtons = buttons else {
return alert
}
for button in alertButtons {
let alertAction = UIAlertAction(title: button.title, style: button.style, handler: {action in
button.handler()
})
alert.addAction(alertAction)
}
return alert
}
}
for my buttons I have a struct:
struct ButtonsAction {
let title: String!
let style: UIAlertAction.Style
let handler: () -> Void
}
In one of my viewControllers I have a function which shows the alert. In that function I have a title and a message then I want to have 1 button to dismiss the alert. The function is something like this:
func fetchFaild(title: String, message: String) {
let buttons = ButtonsAction.init(title: "cancel", style: .cancel, handler: {action in
//here I want to dissmiss the alert I dont know how
})
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert).showAlert(buttons: buttons)
alert.show(self, sender: nil)
}
I have problem adding buttons to the Alert and I don't know how to add actions to the buttons.
I know this is not the best practice here. If any one knows any example or any tutorial that can help me achieve this I really appreciate it.
An extension of UIViewController might be a more reasonable solution and the ButtonsAction struct seems to be redundant.
extension UIViewController {
func showAlert(title: String, message: String, actions: [UIAlertAction], completion: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach{alertController.addAction($0)}
self.present(alertController, animated: true, completion: completion)
}
}
class MyController : UIViewController {
func fetchFailed(title: String, message: String) {
let actions = [UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
print("Cancel tapped")
})]
showAlert(title: title, message: message, actions: actions)
}
}
I'm trying to fire an alert that asks if you want to save or delete a draft after pressing cancel. I'm quite close, but I can't seem to get it right.
I'm unwinding from 'ReplyMailViewController'(ViewController A) to 'MailContentViewController'(ViewController B).
I added the following code in ViewController A to show the alert and 'hold' the segue perform:
override func shouldPerformSegue(withIdentifier identifier: String?, sender: Any?) -> Bool {
if let ident = identifier {
if ident == "cancelDraft" {
let saveDraftActionHandler = { (action:UIAlertAction!) -> Void in
NSLog("EXIT")
}
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let deleteDraftAction = UIAlertAction(title: "Delete Draft", style: .destructive, handler: nil)
alertController.addAction(deleteDraftAction)
let saveDraftAction = UIAlertAction(title: "Save Draft", style: .default, handler: saveDraftActionHandler)
alertController.addAction(saveDraftAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
return false
}
}
return true
}
The segue holds with this code, but the issue is that I can't figure out how to continue the unwind segue after pressing 'Save Draft' for example.
I also have an unwind function in View Controller B, but I can't seem to figure out how I can use this one for this task:
#IBAction func cancelToMailContentViewController(_ segue: UIStoryboardSegue) {
}
Instead of perform the segue directly you need to show your UIAlertViewController first and according to the user response execute your segue or not
#IBAction func showAlertViewController(){
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let replyAction = UIAlertAction(title: "Delete Draft", style: .destructive, handler: nil)
let replyAllAction = UIAlertAction(title: "Save Draft", style: .default) { (action) in
//Do whatever you need here
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
self.performSegue(withIdentifier: "cancelDraft", sender: action) //executing the segue on cancel
}
alertController.addAction(replyAllAction)
alertController.addAction(replyAction)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
}
After this you only need to change the unwind segue action to execute this method, and your segue will be executed if you press cancel in the UIAlertViewController via self.performSegue(withIdentifier: #<SegueIdentifier>, sender: #<sender>)
First, make the alert with two options:
class ViewController: UIViewController {
#IBAction func showAlertButtonTapped(_ sender: UIButton) {
// create the alert
let alert = UIAlertController(title: "UIAlertController", message: "Save this work?", preferredStyle: UIAlertControllerStyle.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: "Hell Yeah", style: UIAlertActionStyle.default, handler: nil))
alert.addAction(UIAlertAction(title: "Hell No", style: UIAlertActionStyle.cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
After this, you have to make the segue and then name it (also connect it by control dragging from the view controller yellow icon to the other view controller):
After that put this your code to execute the segue:
self.performSegue(withIdentifier: ":)", sender: self)
After that you are going to execute the segue when the user responds to the alert:
if buttonTitle == "Hell Yeah" {
elf.performSegue(withIdentifier: ":)", sender: self)
}
so, in the end, your code should look like this:
class ViewController: UIViewController {
#IBAction func showAlertButtonTapped(_ sender: UIButton) {
// create the alert
let alert = UIAlertController(title: "UIAlertController", message: "Save this work?", preferredStyle: UIAlertControllerStyle.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: "Hell Yeah", style: UIAlertActionStyle.default, handler: nil))
alert.addAction(UIAlertAction(title: "Hell No", style: UIAlertActionStyle.cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
if buttonTitle == "Hell Yeah" {
self.performSegue(withIdentifier: ":)", sender: self)
}
}
}
I'm trying to make an unwind segue depend on the user approving it in an alert dialog. However, when I run the code the dialog just quickly appears, disappears, and segues to the other view controller. I've tried making the alertDialog an instance variable of the class and that doesn't make a difference. The code below is inside a UIViewController subclass. What am I missing?
Thanks.
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
var shouldPerformSeque = true
if identifier == "startOver" {
let alertDialog = UIAlertController(title: "Warning",
message: "This will discard all data entered.",
preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertDialog.addAction(okAction)
let cancelActionHandler = {
(action: UIAlertAction!) -> Void in
shouldPerformSeque = false
}
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel,
handler: cancelActionHandler)
alertDialog.addAction(cancelAction)
presentViewController(alertDialog, animated: false, completion: nil)
}
return shouldPerformSeque
}
You can't do this in shouldPerformSegueWithIndentifier because the dialog will be presented asynchronously with regard to that method; by the time the action handler closure executes the method has already returned.
You need to display the dialog in response to whatever is triggering the segue and then perform the unwind programmatically depending on the user's response.
The issue at hand is the following: I show a UIAlertController. If the user presses delete, the view should go back to the first page, which is of the class class TableViewQuery: PFQueryTableViewController.
import UIKit
class DetailViewController: UIViewController {
var currentObject : PFObject?
#IBAction func pressDelete(sender: AnyObject) {
let deleteUser = UIAlertController(title: "Are you sure?", message: "Are you sure you want to delete the current user?", preferredStyle: UIAlertControllerStyle.Alert)
deleteUser.addAction(UIAlertAction(title: "Yes", style: .Default, handler: { (action: UIAlertAction) -> Void in
// Unwrap current object
if let object = self.currentObject {
object["firstName"] = self.firstName.text
object["lastName"] = self.lastName.text
object.deleteEventually()
}
}))
deleteUser.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction) -> Void in
//Do nothing
}))
presentViewController(deleteUser, animated: true, completion: nil)
}
I have tried self.dismissViewControllerAnimated(true, completion: nil), but that just closes the UIAlertController.
I also tried the unwind segue, but then it does that instantly when I press the button, instead of showing the UIAlertController.
I also tried the segueForUnwindingToViewController(<toViewController: UIViewController:UIViewController>, fromViewController: <#T##UIViewController#>, identifier: <#T##String?#>) BUT my toViewController isn't a UIViewController, but a PFQueryTableViewController.
Any ideas on what else I can try?
You should put code into handler of UIAlertAction to respond when user click YES. I guess your are using master detail view controllers. If so, you need to find the right navigation controller to pop. Else, If you are using navigation controller, just pop as the code I commented.
#IBAction func pressDelete(sender: AnyObject) {
let deleteUser = UIAlertController(title: "Are you sure?", message: "Are you sure you want to delete the current user?", preferredStyle: UIAlertControllerStyle.Alert)
deleteUser.addAction(UIAlertAction(title: "Yes", style: .Default, handler: { (action: UIAlertAction) -> Void in
// If you are using master detail view controllers
if let navController = self.splitViewController?.viewControllers[0] as? UINavigationController {
navController.popViewControllerAnimated(true)
}
// If you using navigation controller
// self.navigationController?.popViewControllerAnimated(true)
}))
deleteUser.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction) -> Void in
//Do nothing
}))
presentViewController(deleteUser, animated: true, completion: nil)
}
You should use the completion handler for presentViewController. You may need to keep track of any action the user took with the alert view controller. You will have to keep track of your user's choices with the alert view controller actions. Then in the completion handler check the user's choice and then make your navigation accordingly.
var didTapDeleteButton = false
#IBAction func pressDelete(sender: AnyObject) {
let deleteUser = UIAlertController(title: "Are you sure?", message: "Are you sure you want to delete the current user?", preferredStyle: UIAlertControllerStyle.Alert)
deleteUser.addAction(UIAlertAction(title: "Yes", style: .Default, handler: { (action: UIAlertAction) -> Void in
// Unwrap current object
if let object = self.currentObject {
object["firstName"] = self.firstName.text
object["lastName"] = self.lastName.text
object.deleteEventually()
didTapDeleteButton = true
}
}))
deleteUser.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction) -> Void in
didTapDeleteButton = false
}))
presentViewController(viewController, animated: true, completion: {
// Make navigation choice here
if didTapDeleteButton == true {
// Navigation option 1
} else {
// Navigation option 2
}
})
This is my current code:
import UIKit
class classViewController: UIViewController {
// The function i want to call in other view controllers..
func alertView(title: String, message: String) {
var alert:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
In the other view controller, where I've made an IBAction to perform this alertView, I have done this:
#IBAction func button(sender: AnyObject) {
classViewController().alertView("title", message: "message")
}
When I run the app, after tapping the button I get this error, but no alertView:
Warning: Attempt to present on
whose view is not in the
window hierarchy!
Right. If you want to make a global class that displays alerts, you need to pass in a reference to the current view controller, and use that instead of "self" in calls like presentViewController.
Your class should probably not be a subclass of UIViewController, since it looks like you're never displaying it to the screen.
I created a Utils class that is a subclass of NSObject.
It has a method showAlertOnVC that looks like this:
class func showAlertOnVC(targetVC: UIViewController?, var title: String, var message: String)
{
title = NSLocalizedString(title, comment: "")
message = NSLocalizedString(message, comment: "")
if let targetVC = targetVC
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okButton = UIAlertAction(
title:"OK",
style: UIAlertActionStyle.Default,
handler:
{
(alert: UIAlertAction!) in
})
alert.addAction(okButton)
targetVC.presentViewController(alert, animated: true, completion: nil)
}
else
{
println("attempting to display alert to nil view controller.")
println("Alert title = \(title)")
println("Alert message = \(message)")
}
}