Add a condition to a modal push segue [duplicate] - ios

This question already has answers here:
Perform segues when I want
(2 answers)
Closed 4 years ago.
I use a navigation controller and modal show segues.
I want to block the back button of my second view controller if [condition] isn't true, for exemple by adding an alert "You can't go back until [condition]" when the user press the back button.
Don't know if it's possible, if someone know how to solve it !
Thanksss

You need to add a custom action for your back bar button item.
#IBAction func backPressed(_ sender: UIBarButtonItem) {
if !myCondition {
let alert = UIAlertViewController(title: "Alert", message: "Please fulfill the condition")
let action = UIAlertAction(title: "OK", .default)
self.present(alert, animated: true)
return
}
navigationController?.popViewController(animated: true)
}
Or you can just use normal target action from code.
myBarButton.addTarget(self, selector: #selector(backButtonPressed(_:)))
Sorry for incorrect syntax.

Need back button image "ic-menu-back-primary"
override func viewDidLoad() {
super.viewDidLoad()
// Nav Back Button
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: #imageLiteral(resourceName: "ic-menu-back-primary"), style: .plain, target: self, action: #selector(back(_:)))
navigationItem.leftBarButtonItem = backButton
}
#IBAction func back(_ sender: Any?) {
let alert = UIAlertController(title: nil, message: "You can't go back until [condition]", preferredStyle: .alert)
let resume = UIAlertAction(title: "Cancel", style: .cancel)
let cancel = UIAlertAction(title: "Exit", style: .default) { (action) in
self.navigationController?.popViewController(animated: true)
}
alert.addAction(resume)
alert.addAction(cancel)
present(alert, animated: true)
}

Related

Check if UIAlertController action sheet is dismissed through cancel button?

I have implemented action sheet though alert controller. I want to display a button like cancel button with "Pay" Text written on it.
Issue is makeCall() function call when pay button is clicked , And when rest of the screen is tapped makeCall() function is called again.
How can I identify that action is called through pay button action or through Tapp on rest of the screen? I only want to make call to makeCall() function when pay button is tapped.
alertController = UIAlertController(title: "", message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Pay", style: .cancel) { (UIAlertAction) in
printLog("cancelAction")
makeCall()
}
cancelAction.isEnabled = false
alertController.addAction(cancelAction)
self.present(alertController, animated: true) {}
Here, alert controller view userInteraction disable so when tap outside alert controller not close.
You can do like this:
self.present(alertController, animated: true){
if let mainView = alertController.view.superview?.subviews[0]{
mainView.isUserInteractionEnabled = false
}
}
OR
self.present(alertController, animated: true) {
if let allContainerView = alertController.view.superview?.subviews{
for myview in allContainerView{
if (myview.gestureRecognizers != nil){
myview.isUserInteractionEnabled = false
}
}
}
}
I hope it will work for you.
I don't find any solution that how can you identify that action is called through pay button action or through Tap on rest of the screen.
but the alter solution is that you add tap gesture on rest view. So, you can identify that if cancel button tap or tap in rest of the screen.
alertController = UIAlertController(title: "", message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Pay", style: .cancel) { (UIAlertAction) in
printLog("cancelAction")
makeCall()
}
alertController.addAction(cancelAction)
self.present(alertController, animated: true) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissAlertController))
alertController.view.superview?.subviews[0].addGestureRecognizer(tapGesture)
}
#objc func dismissAlertController(){
self.dismiss(animated: true, completion: nil)
print("through Tap on rest of the screen")
}

How to use alert to confirm or cancel view transition?

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.

How to prevent a UIViewController from being popped out?

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.

Forcing user to select a row in tableview before navigating

I want to ensure user selects a row from a list of rows (row of schedules) displayed in a tableview controller before navigating to another controller. So in the didSelectRow method, I set a boolean variable scheduleSelected to true. In my viewWillDisappear, I check on scheduleSelected and it it is false then I raise an alert and reload the tableview so I stay on the same tableview instead of navigating. It is not working it navigates to another controller anyways but does raise an alert which is too late.
How can I force the user to select a row before navigating out of the current tableview controller?
May be there is easier way instead of this cumbersome procedure. Please let me know.
override func viewWillDisappear(_ animated: Bool) {
if (scheduleSelected == false ) {
let alertController = UIAlertController(title: "UIAlertController", message: "Select Row", preferredStyle: .alert)
alertController.message = "Choose a Schedule"
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true, completion: nil)
self.tableView.reloadData()
}else {
let viewController = storyboard?.instantiateViewController(withIdentifier: "Profiles" )
UIApplication.shared.keyWindow?.rootViewController?.navigationController
let tabController = UIApplication.shared.keyWindow?.rootViewController as! ViewTabBarController
let navController = tabController.selectedViewController as! UINavigationController
navController.popToViewController(viewController!, animated: true)
}
}
How you are navigating out of the current tableview controller using button action or using segue.
If you are using segue, then you have to handle this code like below:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (scheduleSelected == false ) {
let alertController = UIAlertController(title: "UIAlertController", message: "Select Row", preferredStyle: .alert)
alertController.message = "Choose a Schedule"
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true, completion: nil)
self.tableView.reloadData()
}
}

UIAlertController removes tabbar on segue

I have an alertController:
let alertController = UIAlertController(title: "Success", message: "Your book has been uploaded", preferredStyle: .alert)
let PostBook = UIAlertAction(title: "OK", style: .cancel, handler: { action in self.performSegue(withIdentifier: "PostBook", sender: nil)})
alertController.addAction(PostBook)
self.present(alertController, animated: true, completion: nil)
print("Posted to Firebase. ")
In the UIAlertAction, I have an action to segue, and every time I segue, it whites out the tab bar. The tab bar is still there, it justs whites it out, which I don't want. The segue is a show segue.
before segue:
after segue:
Embed in a navigationController to your view so that all the segues goes through the navigationController instead.
Second view controller:

Resources