UIAlertAction's handler doesn't work - ios

I'm new to swift and now following a tutorial book. The book is kind outdated, and I had this bug.
Basically I'm trying to do a table cell selection, after I selected the cell it should pop up a menu and then I can hit the call button. However, right now after I hit the call button, the expected alter box doesn't show up, and the compiler gives me the error: Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior
There is no bug when I edit the code, it just doesn't run correctly.
Another problem is that I have about 17 rows on the table, the stimulator's screen only shows 7 and I can't scroll down to see the rest of them.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath){
//this creates an option menu when you tap on the row
let optionMenu = UIAlertController(title: nil, message: "What do you want to do?", preferredStyle: .ActionSheet)
//this is what happened after you hit the cancel option
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
//defines the action after tap on the call option
let nocallAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
let callActionHandler = {(action:UIAlertAction!)-> Void in
let alertMessage = UIAlertController(title: "Service Unavaliable", message: "Sorry, the call is not avaliable yet, please retry later.", preferredStyle: .Alert)
alertMessage.addAction(nocallAction)
}
//this is what happened after you hit the call option
let callAction = UIAlertAction(title: "Call " + "123-000-\(indexPath.row)", style:
UIAlertActionStyle.Default, handler: callActionHandler)
//this is what happened after you hit the been there option
let isVisitedAction = UIAlertAction(title: "I've been here", style: .Default, handler: {
(action:UIAlertAction!) -> Void in
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.accessoryType = .Checkmark
})
//add the action to the option menu
optionMenu.addAction(isVisitedAction)
optionMenu.addAction(callAction)
optionMenu.addAction(cancelAction)
self.presentViewController(optionMenu, animated: true, completion: nil)
}

You never present the view controller in the callActionHandler - it should look like this:
let callActionHandler = {(action:UIAlertAction!)-> Void in
let alertMessage = UIAlertController(title: "Service Unavaliable", message: "Sorry, the call is not avaliable yet, please retry later.", preferredStyle: .Alert)
alertMessage.addAction(nocallAction)
self.presentViewController(alertMessage, animated: true, completion: nil)
}
Swift also allows you to create the completion handler at the same time as the UIAlertAction, which I think is more readable:
let callAction = UIAlertAction(title: "Call " + "123-000-243534", style: UIAlertActionStyle.Default)
{ action in
let alertMessage = UIAlertController(title: "Service Unavaliable", message: "Sorry, the call is not avaliable yet, please retry later.", preferredStyle: .Alert)
alertMessage.addAction(nocallAction)
self.presentViewController(alertMessage, animated: true, completion: nil)
}

Related

Swift UIKit - Showing an alert after another one

I am trying to show an alert after another one in swift. I found a solution but I think this is not the true way. My code piece is below.
With this code, alerts can be shown in the view.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let person = people[indexPath.item]
let questionAc = UIAlertController(title: "What is your purpose?", message: "", preferredStyle: .alert)
let deleteButton = UIAlertAction(title: "Delete", style: UIAlertAction.Style.destructive) { [weak self] _ in
self?.people.remove(at: indexPath.item)
collectionView.reloadData()
}
let renameButton = UIAlertAction(title: "Rename", style: .default) { [weak self] _ in
let ac = UIAlertController(title: "Rename person", message: nil, preferredStyle: .alert)
ac.addTextField()
ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
ac.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self, weak ac] _ in
guard let newName = ac?.textFields?[0].text else {return}
person.name = newName
self?.collectionView.reloadData()
}))
self?.present(ac, animated: true)
}
questionAc.addAction(deleteButton)
questionAc.addAction(renameButton)
present(questionAc, animated: true)
}
As you know, if I directly add my alert code after the first one, it doesn't work. But if I put my second one's code to first one's action, it does.
However, in this case, I need to connect one alert to another one's button's action. I don't want to connect it to another one.
Is there any solution for better way showing alerts in the same view, orderly?
Thank you.
Presenting the second alert works when done in the handler of one of the first alert's actions because the handler is executed after the alert is dismissed.
With this in mind, you just need to dismiss your first alert then present your second alert in the completion block of the dismissal call, for example:
(Here we just wait 2.5 seconds before presenting the second alert)
func showAlert1() {
let alert = UIAlertController(title: "Alert!", message: "I am alert number 1", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Action", style: .default, handler: { _ in
print("Alert 1 action pressed")
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
// Wait 2.5 seconds then dismiss the first alert and present the second alert
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2500), execute: { [weak self] in
alert.dismiss(animated: true) {
self?.showAlert2()
}
})
}
If you want the first alert to remain open (so that it is still visible when the second alert is dismissed) you can just use the first alert as the presenting ViewController for example:
func showAlert1() {
let alert = UIAlertController(title: "Alert!", message: "I am alert number 1", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Action", style: .default, handler: { _ in
print("Alert 1 action pressed")
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
// Wait 2.5 seconds then present the second alert
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2500), execute: {
self.showAlert2(from: alert)
})
present(alert, animated: true)
}
func showAlert2(from viewController: UIViewController) {
let alert = UIAlertController(title: "Alert!", message: "I am alert number 2", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Action", style: .default, handler: { _ in
print("Alert 2 action pressed")
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
viewController.present(alert, animated: true)
}

Show two UIAlertController objects continuously using completion block

I'm trying to show two UIAlertController's instances continuously, which is like this code block below.
func showAlerts() {
let alertA = UIAlertController(title: "Alert A", message: "This is alert a...", preferredStyle: .alert)
let alertB = UIAlertController(title: "Alert B", message: "This is alert b...", preferredStyle: .alert)
let alertButton1 = UIAlertAction(title: "OK", style: .default, handler: nil)
let alertButton2 = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertA.addAction(alertButton1)
alertA.addAction(alertButton2)
alertB.addAction(alertButton1)
alertB.addAction(alertButton2)
self.present(alertA, animated: true) {
self.present(alertB, animated: true, completion: {
debugPrint("alerts are all shown")
})
}
}
I expect this code to show each alert continuously, which means alertB shows after alertA. But alertB doesn't appear as I expect, with warnings on console saying;
Warning: Attempt to present <UIAlertController: 0x7f7ffde0ace0> on <ContinuousUIAlertController_Experiment.ViewController: 0x7f7ffdd092d0> which is already presenting <UIAlertController: 0x7f7ffde09f90>
If I remember correctly, multiple UIAlertController objects cannot be existed at the same time. So I somehow understand what the warning above tells.
So, then, how can I implement continuous alert showing using completion of UIViewController::present(_:animated:completion:) or with nearly the same logic? (I prefer not to use UIAlertAction's handler)
If there is a solution, please let me know.
I'm struggling with this problem for a few days and I've not addressed yet.
You can't show 2 uialettcontrol at 1time.
But you can show 2nd uialettcontrol on 1st one's uialeraction.
For example
let alertController = UIAlertController(title: "iOScreator", message:
"Hello, world!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Destructive,handler: { action in
self.pressed()
}))
func pressed()
{
print("you pressed")
}
On pressed event you can write code for 2nd uialettcontrol.

Trying to present 2 UIAlertControllers back to back

I have a UIAlertController in which the options are populated from an array and are presented to the user. The user then selects an option from the alert. After this, I have a separate alert that provides the user with a confirmation message that has an okay button.
myAlert.addAction(UIAlertAction.init(title: item, style: .Default, handler: {
(UIAlertAction) in
self.chosenBusiness.append(businessNameData[item]!)
}))
self.presentViewController(myAlert, animated: true, completion: nil)
The code above gathers the data from the array and pushes it into actions in myAlert. The code above is inside of a for loop.
After this I use a function to retrieve the topmost view controller, and then push the next alert.
let top = topMostController()
let alertController = UIAlertController(title: "Location pinned", message: "You've successfully pinned this location, good work!", preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
(result : UIAlertAction) -> Void in
print("OK")
}
alertController.addAction(okAction)
self.presentViewController(myAlert, animated: true, completion: nil)
top.presentViewController(alertController, animated: true, completion: {
_ in
})
The error I receive is:
Attempting to load the view of a view controller while it is
deallocating and is not allowed and may result in undefined behavior.
UIAlertController: 0x1535b1cd0.
Can someone help me with this?
I think this is what you are looking for. The second must be called with the dismissal action of the first. Also, anytime you work with UI, It is safer to use dispatch_async(dispatch_get_main_queue()) {
\\code }
than not if you are not positive you are currently on the main queue.
let firstAlertController = UIAlertController(title: "First", message: "This is the first message.", preferredStyle: UIAlertControllerStyle.Alert)
let secondAlertController = UIAlertController(title: "Second", message: "This is the second message.", preferredStyle: UIAlertControllerStyle.Alert)
let secondDismissAction = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, completion: nil)
secondAlertController.addAction(secondDismissAction)
let firstDismissAction = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(secondAlertController, animated: true, handler: nil)
}
}
firstAlertController.addAction(firstDismissAction)
self.presentViewController(firstAlertController, animated: true, completion: nil)

How to make a UIAlert button press advance to next Storyboard?

I'm quite new to Swift (and coding in general) and to this website.
I'm having a little bit of an issue now. In my app, I have an alert when a timer reaches 0. In that alert, there are 2 buttons. One says "Share" and the other says "Continue". I want to make it such that when a user taps "Continue", the next Storyboard will be shown to them. As of now, whatever button I press will dismiss the alert, but stay on the same Storyboard. (It also prints to the console which button I pressed, but of course that's just for me to see).
How do I go about doing this? Here is my code, in case anyone wants to know.
let alert = UIAlertController(title: "Time's Up!", message: "What would you like to do now?", preferredStyle: .Alert)
let firstAction = UIAlertAction(title: "Continue", style: .Default) { (alert: UIAlertAction!) -> Void in
NSLog("You pressed button one")
}
let secondAction = UIAlertAction(title: "Share", style: .Default) { (alert: UIAlertAction!) -> Void in
NSLog("You pressed button two")
}
alert.addAction(firstAction)
alert.addAction(secondAction)
presentViewController(alert, animated: true, completion:nil)
Try with this:
// Create the alert controller
var alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .Alert)
// Create the actions
var okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("someViewController") as UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
var cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
//do whatever you want here
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.presentViewController(alertController, animated: true, completion: nil)
If you haven't already set your ViewControllerIdentifier:
You need to set the Identifier value that you can find on the right column
Here is the code that might help you. When you create a AlertController with actions, you can provide the actions and style of the button when you define them. In code below action is in the closure (Block) and style is defined as .Default or .Cancel
let alert = UIAlertController(title: "Time's Up!", message: "What would you like to do now?", preferredStyle: .Alert)
let firstAction = UIAlertAction(title: "Continue", style: .Default) { (alert: UIAlertAction!) -> Void in
// Action when you press button goes here
print("Here you show next storyboard item.")
// Code to push new ViewController. For example :
self.navigationController?.pushViewController(newVCInstance, animated: true)
}
let secondAction = UIAlertAction(title: "Share", style: .Default) { (alert: UIAlertAction!) -> Void in
print("Here you share things.")
// Code to share things.
}
let thirdAction = UIAlertAction(title: "Cancel", style: . Cancel) { (alert: UIAlertAction!) -> Void in
print("Just dismissing the Alert Controller.")
}
alert.addAction(firstAction)
alert.addAction(secondAction)
alert.addAction(thirdAction)
presentViewController(alert, animated: true, completion:nil)

Go to previous controller in navigation stack (Swift)

I am trying to popViewController to the previous View Controller in the navigation stack after an alertView. As of now, my program will not run the popViewController method, since the alertView is in the way, and brings up the error:
UINavigationController 0x17670900 while an existing transition or presentation is occurring; the navigation stack will not be updated.
How do I go about running the popViewController method after the user clicks OK from the alertView? Do I have to set a delegate which detects when the used clicks OK?
Here is my code:
//alertView after Picture saved
let alertView = UIAlertController(title: "Success!", message: "Record Saved to Database", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
//go to previous controller using popViewController, doesnt work, brings up error message
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
You need to pop the view controller when user presses the Ok button from AlertView.
let alertView = UIAlertController(title: "Success!", message: "Record Saved to Database", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "Ok", style: .Default) { (action) in
// pop here
if let navController = self.navigationController {
navController.popViewControllerAnimated(true)
}
}
alertView.addAction(OKAction)
self.present(alertView, animated: true, completion: nil)
You can put the "popViewControllerAction" inside an alert action like this.
func alertMethod() {
var okAlertController = UIAlertController(title: NSLocalizedString("Your title", comment: ""), message: "Your message", preferredStyle:
UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: UIAlertActionStyle.Default) { (UIAlertAction) -> Void in
// your action - navController.popViewControllerAnimated(true)
}
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: UIAlertActionStyle.Default) { (UIAlertAction) -> Void in
// your action
}
}
saveAlertController.addAction(okAction)
saveAlertController.addAction(cancelAction)
self.presentViewController(okAlertController, animated: true, completion: nil)
}
That should work, in that case the method gets called after the user presses a button...

Resources