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.
Related
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)
})
}
}
I want to make a "rate app" alert, but for some reason it gets deallocated before showing it.
Here's the code:
func showAlert() {
if #available(iOS 8.0, *)
{
let alertController = UIAlertController(title: "Rate App", message: "Rate this app now", preferredStyle: .Alert)
let neverAction = UIAlertAction(title: "Never Show This Again", style: .Destructive, handler: { (action: UIAlertAction) in
self.userDefaults.setBool(true, forKey: "rateAlertRejected")
})
let rateAction = UIAlertAction(title: "Rate Now", style: .Default, handler: { (action: UIAlertAction) in
// Rate App
})
let remindAction = UIAlertAction(title: "Remind Me Later", style: .Cancel, handler: nil)
alertController.addAction(rateAction)
alertController.addAction(neverAction)
alertController.addAction(remindAction)
presentViewController(alertController, animated: true, completion: nil)
}
else
{
// Identical code (using UIAlertView) for iOS 7 which works perfectly
}
}
The method executes (in certain conditions, but for testing purposes it does it every time) after a custom unwind segue.
Why do I have this problem? I used UIAlertController before but I had no issues.
According to your comment you show showAlert in
a method that executes after a custom unwind segue.
unwind segue dismisses the view heriarchy and therefore your alert
does not get reference to a view controller to show from.
To solve this, show your alert in the View controller you unwind to or wait for the alert controller action to be completed before unwinding.
Edit: Thank you for the replies, yes I needed an alert and not an action sheet! I have implemented this new code and it works, but is there a reason why it segues to the next view before the user can enter a ride title?
Also it throws this message in the debug console, should I be concerned?
2016-02-16 12:30:21.675 CartoBike[687:128666] Presenting view controllers on detached view controllers is discouraged .
#IBAction func stopAction(sender: UIButton) {
let alert = UIAlertController(title: "Ride Stopped", message: "Give a title to your ride", preferredStyle: .Alert)
let saveAction = UIAlertAction(title: "Save", style: .Default,
handler: { (action:UIAlertAction) -> Void in
// Allow for text to be added and appended into the RideTableViewController
let textField = alert.textFields!.first
rideContent.append(textField!.text!)
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addTextFieldWithConfigurationHandler {
(textField: UITextField) -> Void in
}
alert.addAction(saveAction)
alert.addAction(cancelAction)
// Save the ride
saveRide()
// Automatically segue to the Ride details page
self.performSegueWithIdentifier("ShowRideDetail", sender: nil)
presentViewController(alert, animated: true, completion: nil)
timer.invalidate()
self.stopLocation()
}
Overview:
I am working my way through my first real app. The basic logic is a home screen to start a new ride or view previous rides. Starting a ride will open a new view with a map to record a bicycle ride based on the users location, this can be stopped and save the ride and immediately switch to a new view to see a map of the ride. Alternatively from the home screen the user can select previous rides and view a list of their old rides in a table view and select one and transition to a detailed view with a map of their ride.
Problem: When adding a UIAlertAction I would like there to be a save and cancel feature. In addition, I would like the user to be able to add a custom title by typing it in via a text field. The input from the text field will be appended to a global variable called rideContent that is tied to the creation of new cells in the table view to store multiple bike rides by unique title.
Research:
I have reviewed the questions titled "How to add a TextField to UIAlertView in Swift" & "Writing handler for UIAlertAction" and still can't seem to discern what I have done wrong. Ideally the alert within this screenshot of an app from the raywenderlich site is what I would like it to look like. I am not sure if what I am trying to do is even possible since there a so many view controllers involved. Granted I am new to swift and I'm sure I am missing something obvious!
Currently getting an error of
"Type of expression is ambiguous without more context", see screenshot: here
Here is the UIAlertController code:
// The timer pauses & the location stops updating when the the stop button is pressed
#IBAction func stopAction(sender: UIButton) {
var inputTextField: UITextField?
let actionSheetController = UIAlertController (title: "Ride Stopped", message: "Add a title to your ride", preferredStyle: UIAlertControllerStyle.ActionSheet)
// Add a cancel action
actionSheetController.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
// Add a save action
actionSheetController.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.Default, handler: {
(actionSheetController) -> Void in
//Add a text field --- Getting an error on this line
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
// you can use this text field
inputTextField = textField
// Append the ride title to the table view
rideContent.append(textField!.text!)
// Update when user saves a new ride to store the ride in the table view for permanent storage
NSUserDefaults.standardUserDefaults().setObject(rideContent, forKey: "rideContent")
// Save the ride
self.saveRide()
// Automatically segue to the Ride details page
self.performSegueWithIdentifier("ShowRideDetail", sender: nil)
}}))
//present actionSheetController
presentViewController(actionSheetController, animated: true, completion: nil)
timer.invalidate()
self.stopLocation()
}
Thank you stack overflow for any help you may offer!
You're using an .ActionSheet while the tutorial you showed is using an .Alert
Action Sheets can have buttons but not text fields.
"Alerts can have both buttons and text fields, while action sheets only support buttons."
NSHipster
Use your tableview Array when Add Button is pressed and "Add alertview textfirld to tableview array and then reload tableview".
#IBAction func btnAdd(_ sender: Any) {
let alertController = UIAlertController(title: "Add Category", message: "", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Add", style: .default, handler: { alert -> Void in
let firstTextField = alertController.textFields![0] as UITextField
self.categories.add(firstTextField.text!)
self.tblCatetgory.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: {
(action : UIAlertAction!) -> Void in })
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Enter Category!!!"
}
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
}
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
}
})
I have a button in each cell in a tableviewcell. When the button is clicked, in the cell coding page I had an action which was working already. However I decided to let users confirm it before the action starts, so added an UIAlertController inside the action. The alert controller works fine in a UIviewcontroller, but in a UItableviewcell with the code below I got the error message: "does not have a member named presentviewcontroller". How can I make it work? My code is here. Thanks
#IBAction func followBtn_click(sender: AnyObject) {
if title == "Following" {
var refreshAlert = UIAlertController(title: "Sure?", message: "Do you want to unfollow this person?", preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction(UIAlertAction(title: "Yes", style: .Default, handler: { (action: UIAlertAction!) in
var query = PFQuery(className: "follow")
query.whereKey("user", equalTo: PFUser.currentUser()!.username!)
query.whereKey("userToFollow", equalTo: usernameLbl.text!)
var objects = query.findObjects()
for object in objects! {
object.delete()
}
}))
refreshAlert.addAction(UIAlertAction(title: "No", style: .Default, handler: { (action: UIAlertAction!) in
println("Cancel unfollow")
}))
self.presentViewController(refreshAlert, animated: true, completion: nil)
/* self.window?.rootViewController?.presentViewController(refreshAlert, animated: true, completion: nil)
when I use the above to make it run, the code runs but nothing happens and I get the warning below in the output area
2015-07-02 08:57:22.978 MyLast[968:200872] Warning: Attempt to present <UIAlertController: 0x7f849c04ed20> on <UINavigationController: 0x7f8499c69020> whose view is not in the window hierarchy!
*/
}
else {
println(“rest will work”)
/* rest of the code */
}
}
Make sure your tableview controller is in hierarchy of navigation controller or embed in UINavigationController. Because UIAlertView is deprecated in swift And there is a UIAlertViewController and whenever you require or to present Alert you must have a Controller Hierarchy.
You also can show ActionSheet using same code by little changes UIAlertControllerStyle is action sheet.
One another suggestions is once try with remove your handler code to nil
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))