I've created UIAlertController with Three action buttons, now I want to set text to a TextView on each button.
Problem is this that when I click a button then result of previously clicked button is printed on TextView.
My Code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
mTextView.text = ""
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func showDialogOnClick(_ sender: UIButton) {
//Creating UIAlert, giving its title and message, setting style(alert OR alertSheet)
let mDialog = UIAlertController(title: "Title", message: "This is the message" , preferredStyle: .actionSheet)
//add actions/buttons to alert
mDialog.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: {(action) in
print("Cancel")
self.mTextView.text = "Cancel"
}))
mDialog.addAction(UIAlertAction(title: "Default", style: .default, handler: {(action) in
print("Default")
self.mTextView.text = "Default"
}))
mDialog.addAction(UIAlertAction(title: "Destructive", style: .destructive, handler: {(action) in
print("Destructive")
self.mTextView.text = "Destructive"
}))
self.present(mDialog, animated: true)
}
}
What i think can be possible issue is you are testing this in your Simulator and your computer is having low graphic configuration same thing happens with be when i test my code in my old mac mini.You can do two things :-
Test the code in a physical device
Try to minimize your app in your simulator and come back to the app you'll see the effect Or change the view back and forth to see the effect.
Let me know if I was right.
Related
On certain conditions, if users intent to leave the current view (e.g. pop the current view, push other view, or select other tab items etc.), a UIAlertController should be presented to confirm users' real intention.
Users can press OK to proceed the view transitioning, or Cancel to stay on the current view.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if someConditions {
promptUIAlert()
}
}
Any solutions that can achieve that requirements?
First of all, you cannot handle this is viewWillDisappear because at this point it has already been decided that the view will disappear. You need to handle this wherever you have view transitions (push, pop, present, dismiss).
You have to handle the transitions in the confirmation alert action. You alert would look something like this.
let alert = UIAlertController(title: "Wait!", message: "Are you sure you want to leave this view?", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default) { (alertAction) in
//Handle view transitioning here
}
let cancel = UIAlertAction(title: "Cancel", style: .destructive) { (alertAction) in
//Do nothing?
}
alert.addAction(ok)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
You can't intercept and retain the user in the same ViewController once the viewWillDissaper is fired. You should do it in advance. My suggestion is to add a UIBarButton with a label Back to the navigationItem.leftBarButtonItem and manually check whether the user wants to navigate or not. This is the closest you can get.
also, you can use the UIAlertViewController to confirm whether the user wants to navigate or not.
//inside viewDidLoad
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(handleBack))
//define another function to handle the selctor
#objc func handleCancel(){
if someConditions {
promptUIAlert()
}
}
//you function should be like this with UIAlertController
func promptUIAlert(){
let alertController = UIAlertController(title: "Error", message: "Some message", preferredStyle: .alert)
let CancelAction = UIAlertAction(title: "Cancel", style: .default) { (action) in
//type your custom code here for cancel action
}
let OkAction = UIAlertAction(title: "OK", style: .default) { (action) in
//type your custom code here for OK action
}
alertController.addAction(OKAction)
alertController.addAction(CancelAction)
self.present(alertController, animated: true)
}
Doing something inside viewWillDissaper would be handy to save some unsaved data behind the screen. But not to prompt and ask the user whether they want to remain in the same ViewController.
I have a childViewController which is pushed from parentViewController. In childViewController I want to block pop action in a particular condition.
I wrote this code in viewWillDisappear: But I guess need to do this somewhere else.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if changesMade {
let alertController = UIAlertController(title: "Alert", message: "Changes made are not saved. Do you wish to save changes made?", preferredStyle: .alert)
let cancelOption = UIAlertAction(title: "Cancel", style: .cancel)
let saveOption = UIAlertAction(title: "Save", style: .default, handler: { (action) in
self.saveSession()
})
alertController.addAction(saveOption)
alertController.addAction(cancelOption)
present(alertController, animated: true)
}
}
Actually, there is a very simple solution: navigationItem.hidesBackButton = true - this will hide "BACK" button and disable swipe-to-back feature. 🤓
There are two cases here:
User can pop using back button.
User can pop using interactive pop gesture of navigation controller.
I think you should use a custom back button and name it Done and write your logic of showing alert on press of this button.
Using the custom back button would disable the interactive pop gesture by default and you will be spared from playing the dance of enabling/disabling interactivePopGesture on navigation controller.
block pop action till your changes are not saved like this
if changesMade {
let alertController = UIAlertController(title: "Alert", message: "Changes made are not saved. Do you wish to save changes made?", preferredStyle: .alert)
let cancelOption = UIAlertAction(title: "Cancel", style: .cancel)
let saveOption = UIAlertAction(title: "Save", style: .default, handler: { (action) in
self.saveSession()
self.navigationController?.popViewController(animated: true)
})
alertController.addAction(saveOption)
alertController.addAction(cancelOption)
present(alertController, animated: true)
}
Update - Add this below custom button and its action in child View controller which is being pushed from parent View Controller
so, without satisfying your condition user can not move from child to parent again
For customising action of navigation backButton you need to manually add a back Button using below line , you can Customise barButton being added here in DidLoad
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.backToInitial(sender:)))
It will perform required Action
#objc func backToInitial(sender: AnyObject) {
if changesMade {
let alertController = UIAlertController(title: "Alert", message: "Changes made are not saved. Do you wish to save changes made?", preferredStyle: .alert)
let cancelOption = UIAlertAction(title: "Cancel", style: .cancel)
let saveOption = UIAlertAction(title: "Save", style: .default, handler: { (action) in
self.saveSession()
self.navigationController?.popViewController(animated: true)
})
alertController.addAction(saveOption)
alertController.addAction(cancelOption)
present(alertController, animated: true)
}
}
and I do not think you can stop default back button action task of navigation Controller as it is designed the way to perform it
But yes you can manage it in ViewWillDisappear as :
override func viewWillDisappear(_ animated: Bool) {
if self.isMovingFromParentViewController || self.isBeingDismissed {
self.navigationController?.popViewController(animated: false) //here task but will not result as Expected output
}
}
-----------------Re-Update ---------------
in swift I used a objective-C class to get output as expected now, childViewController pop action is being controller from a alert using default back button that we get from navigation controller
You can customise you pop action to perform or not until your condition is not satisfied
Github Link - https://github.com/RockinGarg/NavigationBackButton.git
I found a much more elegant solution (in my opinion).
It works no matter how the user triggers the pop (accessibility escape, swipe back gesture, tapping back) since it overrides the built in pop methods that the system uses.
Swift 5
public class DiscardSafeNavigationController:UINavigationController {
/// Should the pop be prevented? Set this to `true` when you have changes which need to be protected
public var hasUnsavedChanges:Bool = false
/// Show a prompt on the top most screen asking the user if they wish to proceed with the pop
/// - Parameter discardCallback: The callback to use if the user opts to discard
private func confirmDiscardChanges(discardCallback:#escaping (()->())) {
let alertController = UIAlertController.init(title: "Discard changes", message: "Are you sure you want to discard any unsaved changes?", preferredStyle: UIAlertController.Style.alert)
alertController.addAction(UIAlertAction.init(title: "Discard", style: UIAlertAction.Style.destructive, handler: { (_) in
discardCallback()
//User elected to discard and so, at this point, they no longer have changes to save
self.hasUnsavedChanges = false
}))
alertController.addAction(UIAlertAction.init(title: "Cancel", style: UIAlertAction.Style.cancel, handler: nil))
self.topViewController?.present(alertController, animated: true, completion: nil)
}
override func popViewController(animated: Bool) -> UIViewController? {
//If there aren't unsaved changes, popping is safe
if !hasUnsavedChanges {
return super.popViewController(animated: animated)
}else {
//Changes have been made. Block the pop and first check with the user before continuing
confirmDiscardChanges {
super.popViewController(animated: animated)
}
return nil
}
}
}
and when you want to enable discard protection from any child view controllers, simply use:
(self.navigationController as? DiscardSafeNavigationController)?.hasUnsavedChanges = true
and then any time the navigation controller is asked to pop it, the navigation controller will ask the user first.
I am trying to create an alert controller which has a custom view inside it. The custom view is loaded from xib file. The custom view contains a uiswitch. The problem is that its click event is not being triggered and from UI switch is not turned on/off on click. Here is the code I am trying to work on:
This is the button click event of my view controller to present alert:
#IBAction func btnClicked(_ sender: UIButton) {
let alertController = UIAlertController(title: "\n\n\n\n\n\n", message: "", preferredStyle: UIAlertControllerStyle.alert)
let customView=Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)!.first! as! CustomView
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {(alert: UIAlertAction!) in print("cancel")})
alertController.addAction(cancelAction)
let somethingAction = UIAlertAction(title: "Something", style: .default, handler: {(alert: UIAlertAction!) in print("something")})
alertController.addAction(somethingAction)
alertController.view.addSubview(customView)
self.present(alertController, animated: true, completion:{})
}
This is the code inside CustomView class:
class CustomView: UIView {
#IBAction func switchClicked(_ sender: UISwitch) {
print("switch cliked")
}
}
My CustomView.xib file has the referencing layout properly set. It just has one uiswitch. The CustomView.xib has its size set to 'freeform'(not sure if it matters). I also tried setting isUserInteractionEnabled to false or true at various places for CustomView and/or alertController.view after searching for similar issues but nothing works.
You should be capturing the valueChanged control event but if you want to do the value change programmatically via click I suppose you could do:
class CustomView: UIView {
#IBAction func switchClicked(_ sender: UISwitch) {
sender.setOn(!sender.isOn, animated: true)
}
}
I think xib view will triger method only when same class has been loaded in memory. In your case this might be an issue . To test this u can add custom view in same class in which u are showing alert .
Please help! Im a huge beginner to programming.
I currently have an alert controller set up to enter text into a label when I tap on a button. I am trying to find a quicker way to enter text with preloaded words from a dropdown menu, instead of typing them in. I've seen online that you can do this with a pickerview or with labels/buttons.
My issue is that I would like to have the option to write in my own text, if by chance, I didn't program the right preloaded words. Im a complete noob to Xcode/swift so my best guess is somehow (no idea how) integrating my text-button alert-controller as a button in my dropdown menu, or vice versa, where my dropdown menu is an option in my alert controller? This is the alert controller used to enter text into a label when a button is clicked:
//Text button
#IBOutlet weak var TextLabel: UILabel!
#IBAction func TextButtonTapped(_ sender: UIButton) {
print("Text Button Tapped")
openTextAlert()
}
func openTextAlert() {
//Create Alert Controller
let alert9 = UIAlertController (title: "Whatever Text Your Heart Desires:", message: nil, preferredStyle: UIAlertControllerStyle.alert)
//Create Cancel Action
let cancel9 = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil)
alert9.addAction(cancel9)
//Create OK Action
let ok9 = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { (action: UIAlertAction) in print("OK")
let textfield = alert9.textFields?[0]
print(textfield?.text!)
self.TextLabel.text = textfield?.text!
}
alert9.addAction(ok9)
//Add Text Field
alert9.addTextField { (textfield: UITextField) in
textfield.placeholder = "Whatever text you want to enter"
}
//Present Alert Controller
self.present(alert9, animated:true, completion: nil)
}
I am not directly using textfields in my app as the heigh of the field is too large.
Thank you! :)
This function is called in my viewDidLoad. It doesn't get an error but nothing ever happens. It is definitely getting called though because I told it to print and it worked.
Here is the code for alert:
func makeAlert()
{
let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: UIAlertControllerStyle.Alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.presentViewController(alertController, animated: true, completion: nil)
The problem here is that you are trying to display your alertController before the presenting view controller (the one with your viewDidLoad) is displayed on screen. viewDidLoad() is called after your view controller is loaded into memory, not necessarily when its view is in the view hierarchy.
Therefore, call makeAlert() in viewDidAppear(_:):
override func viewDidAppear(animated: Bool) {
makeAlert()
}
This ensures that your view controller is already displayed and is able to present your alertController.
Reading about viewDidLoad() and viewDidAppear(_:) here is helpful:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/