I have a very simple project: there's only one ViewController and a UIButton. The IBAction for the button is:
var alertViewControllerTextField: UITextField?
var promptController = UIAlertController(title: "Type Something", message: nil, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
print("\(alertViewControllerTextField?.text)")
})
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
//promptController = nil
}
promptController.addAction(ok)
promptController.addAction(cancel)
promptController.addTextField { (textField) -> Void in
alertViewControllerTextField = textField
}
self.present(promptController, animated: true, completion: nil)
When the app finishes launching, memory usage is 14,4 Mb.
When I click the button it reaches 18,4 Mb (if I click again and again the button, it finally reaches 20 Mb).
Anyway, I thought when I clicked the UIAlertController's cancel or ok button, memory would have returned to 14,4, even slowly, but this is not the case.
I thought to make the UIAlertController an optional to have the chance to assign it a nil on close, but the UIAlertController can't be nil because you can't declare it as an optional. I thought to make it a member and declare it with the weak keyword (no luck).
So, is there any way to reduce the memory usage when I click one of the buttons of a UIAlertController?
PLease Put DispatchQueue this code than try i solved this issue by this
DispatchQueue.global(qos: .userInitiated).async
{
self.present(promptController, animated: true, completion: nil)
}
I have seen elsewhere that you need to declare the alertViewControllerTextField weak so that the actions don't retain it. (Not sure I fully understand this yet.)
So try:
var alertViewControllerTextField: UITextField?
var promptController = UIAlertController(title: "Type Something", message: nil, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { [weak alertViewControllerTextField](action) -> Void in
print("\(alertViewControllerTextField?.text)")
})
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { [weak promptController] (action) -> Void in
//promptController = nil
}
promptController.addAction(ok)
promptController.addAction(cancel)
promptController.addTextField { (textField) -> Void in
alertViewControllerTextField = textField
}
self.present(promptController, animated: true, completion: nil)
Related
This question already has answers here:
UIAlertController visible only when viewDidAppear has finished its call
(2 answers)
Closed 4 years ago.
I want to display an alert screen using UIAlertController in viewDidAppear() and wait until the button is pressed (iOS 11).
When displaying UIAlertController by present, the alert screen is called in viewDidAppear(), but it is not displayed and the screen can not be tapped.
Alert screen by asynchronous or delayed execution. The alert screen is displayed without any problem.
Is there any good way to get in sync?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var doneloop = false
let alert = UIAlertController(title:"Title", message: "Message", preferredStyle: UIAlertControllerStyle.alert)
let action1 = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {
(action: UIAlertAction!) in
print("push OK button")
doneloop = true // Runloop flag
})
alert.addAction(action1)
self.present(alert, animated: false, completion: nil)
while !doneloop {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
}
alert.dismiss(animated: false, completion: nil)
}
I have created this method, just pass required data to this function with completion handling.....
//Alert with handling
func showMessageWithAction(_ messageText:String, messageTitle:String, okButtonTitle:String, cancelButtonTitle:String?, vc: UIViewController, completion:#escaping (Bool) -> Void) {
let attribute = NSMutableAttributedString.init(string: messageText)
attribute.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.white , range: NSRange(location: 0,length: messageText.count))
let alert = UIAlertController(title: messageTitle,
message: messageText,
preferredStyle:
UIAlertControllerStyle.alert)
alert.view.tintColor = UIColor(red: (0/255.0), green: (29/255.0), blue: (97/255.0), alpha: 1.0)
alert.addAction(UIAlertAction(title: okButtonTitle,
style: UIAlertActionStyle.default,
handler: {(alert: UIAlertAction!) in
completion(true)
}))
if let cancelButton = cancelButtonTitle {
alert.addAction(UIAlertAction(title: cancelButton,
style: UIAlertActionStyle.cancel,
handler: {(alert: UIAlertAction!) in
completion(false)
}))
}
vc.present(alert, animated: true, completion: nil)
}
The way you do it is pretty messy, there is no need to synchronize it.
Also I would recommend you to run the alert presentation in viewWillAppear instead of viewDidAppear in case that you want see the alert after every entrency into this UIViewController.
In case that you would like to run it once, use viewDidLoad
I've created a wrapper around UIAlertController.
struct AlertWrapper {
static func presentAlert(for vc: UIViewController, with title: String, message: String, dismissAlertAction: UIAlertAction? = nil, applyAlertAction: UIAlertAction? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
if dismissAlertAction != nil {
alert.addAction(dismissAlertAction!)
}
if applyAlertAction != nil {
alert.addAction(applyAlertAction!)
}
vc.present(alert, animated: true, completion: nil)
}
}
Example inside methods above
let applyAction = UIAlertAction(title: "Ok", style: .default) { (_) in
// What ever you would like to do after pressing 'Ok'
}
let dismissAction = UIAlertAction(title: "Cancel", style: .default) { (_) in
// What ever you would like to do after pressing 'Cancel'
}
AlertWrapper.presentAlert(for: self, with: "YOUR TITTLE", message: "YOUR MESSAGE", dismissAlertAction: dismissAction, applyAlertAction: applyAction)
Fell free to ask any questions and next time please read rules here.
Example solution
Simple way
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(title:"Title", message: "Message", preferredStyle: UIAlertControllerStyle.alert)
let action1 = UIAlertAction(title: "OK", style: UIAlertActionStyle.default)
alert.addAction(action1)
self.present(alert, animated: false, completion: nil)
}
I'm trying to make a TextField like the one that is used in banking apps, where while you're typing the amount of money, it starts with $0.00, and if I type a 5 it shows $0.05, and if I type a 4 it'll show $0.54 and so on. So it will fill in each digit from the right as long as you keep typing. I understand that I'll need to use shouldChangeCharactersInRange but my TextField is in the alertController so it makes things a little difficult and I'm not sure how to approach the logic behind it. Here's what I've got so far
#IBAction func myMethod(_ sender: Any) {
let myAlert = UIAlertController(title: "Some words", message: "\n", preferredStyle: .alert)
myAlert.addTextField(configurationHandler: { (textField) -> Void in
textField.placeholder = "TextField1"
})
myAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
myAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
//some action
}))
self.present(myAlert, animated: true, completion: nil)
}
Hopefully I explained everything thoroughly enough.
Thanks for help in advance!
It looks like all that is missing is setting the delegate of that UITextField. Setting the delegate in the configurationHandler should be enough to correct that.
If your shouldChangeCharactersInRange function is in the parent file, set it as the delegate. Otherwise, create your UITextFieldDelegate class with the methods you need, then set an instance of it as your delegate.
class MyTextFieldDelegate: UITextFieldDelegate {
// Implement shouldChangeCharactersInRange and other delegates here
}
...
// View Controller presenting the alert
var textFieldDelegate: MyTextFieldDelegate
override func viewDidLoad() {
// Other stuff here
// Set your delegate
textFieldDelegate = MyTextFieldDelegate()
}
...
#IBAction func myMethod(_ sender: Any) {
let myAlert = UIAlertController(title: "Some words", message: "\n", preferredStyle: .alert)
myAlert.addTextField(configurationHandler: { (textField) -> Void in
textField.placeholder = "TextField1"
textField.delegate = self.textFieldDelegate
})
myAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
myAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
//some action
}))
self.present(myAlert, animated: true, completion: nil)
}
I am building a game.
This game has a UITableViewController which serves as a Settings & Options feature.
One of the options that you can select is to switch the opponent from pass and play to an AI (or the other way around).
What I'd like to do is use a UIAlertController to intercept the touch, check for a game in progress, and prompt the user to cancel the change or continue.
I have implemented the code to do this within the override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) function.
My code is as follows:
if indexPath.section == 1 {
// Here we need to detect a game in progress and ask for a confirmation before switching to another mode.
if getGameInProgress() == true {
// Display choice dialog
alert = UIAlertController (title: alertCaption, message: alertMessage, preferredStyle: .Alert)
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: {(action: UIAlertAction) -> Void in
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
return
})
alert.addAction(cancelAction)
let ok: UIAlertAction = UIAlertAction(title: "Confirm", style: .Default, handler: {(action: UIAlertAction) -> Void in
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
// Do nothing, just let the touch method continue...
})
alert.addAction(ok)
self.presentViewController (alert, animated: true, completion: nil)
}
if indexPath.row == 0 {
// "Pass and Play"
setOpponentPreference("Pass and Play")
}
if indexPath.row == 1 {
// "Virtual Robert"
setOpponentPreference("Virtual Robert")
}
if indexPath.row == 2 {
// "Virtual Nathan"
setOpponentPreference("Virtual Nathan")
}
// No matter which difficulty selected:
gameboard.setDifficulty()
// Create a method to reset the game, but without fading out the menu screen (the current new game method takes you directly to the game. The player may not want that in this instance.)
}
The problem I am having is that the alert controller displays and waits for input, but it does not prevent the rest of the didSelectRowAtIndexPath function form continuing.
How do I receive the users input before handling the touch on the cell?
Hello All you need is put your code inside of your completion section for your "Confirm" option, your code will be execute only when user tap on "Confirm" button of your UIAlertViewController
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
// Do nothing, just let the touch method continue...
})
so your code must be
if indexPath.section == 1 {
// Here we need to detect a game in progress and ask for a confirmation before switching to another mode.
if getGameInProgress() == true {
// Display choice dialog
alert = UIAlertController (title: alertCaption, message: alertMessage, preferredStyle: .Alert)
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: {(action: UIAlertAction) -> Void in
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
return
})
alert.addAction(cancelAction)
let ok: UIAlertAction = UIAlertAction(title: "Confirm", style: .Default, handler: {(action: UIAlertAction) -> Void in
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
if indexPath.row == 0 {
// "Pass and Play"
setOpponentPreference("Pass and Play")
}
if indexPath.row == 1 {
// "Virtual Robert"
setOpponentPreference("Virtual Robert")
}
if indexPath.row == 2 {
// "Virtual Nathan"
setOpponentPreference("Virtual Nathan")
}
// No matter which difficulty selected:
gameboard.setDifficulty()
// Create a method to reset the game, but without fading out the menu screen (the current new game method takes you directly to the game. The player may not want that in this instance.)
})
alert.addAction(ok)
self.presentViewController (alert, animated: true, completion: nil)
}
}
I hope this helps you, for me works great, regards
Sorry for the bad English. What I understand from you question is that you want to hold the program to continue execution till user select one of the button from UIAlertController.
for that you need to do is perform task inside the action of Ok button or Cancel button.
if getGameInProgress() == true {
// Display choice dialog
alert = UIAlertController (title: alertCaption, message: alertMessage, preferredStyle: .Alert)
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: {(action: UIAlertAction) -> Void in
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
return
})
alert.addAction(cancelAction)
let ok: UIAlertAction = UIAlertAction(title: "Confirm", style: .Default, handler: {(action: UIAlertAction) -> Void in
//Do something in here like print
println("Confirm Button Pressed")
self.alert.dismissViewControllerAnimated(true, completion: { _ in })
// Do nothing, just let the touch method continue...
})
alert.addAction(ok)
self.presentViewController (alert, animated: true, completion: nil)
}
hope it help.
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'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)