I need to execute an action (emptying an array), when the back button of a UINavigationController
is pressed, while the button still causes the previous ViewController
on the stack to appear. How could I accomplish this using swift?

- 6,983
- 5
- 16
- 18
32 Answers
Replacing the button to a custom one as suggested on another answer is possibly not a great idea as you will lose the default behavior and style.
One other option you have is to implement the viewWillDisappear method on the View Controller and check for a property named isMovingFromParentViewController. If that property is true, it means the View Controller is disappearing because it's being removed (popped).
Should look something like:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParentViewController {
// Your code...
}
}
In swift 4.2
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
// Your code...
}
}

- 7,107
- 2
- 33
- 56

- 8,682
- 3
- 26
- 39
-
7@gmogames yes, you cannot do that. The question didn't ask for that though. To be able to stop the action of going back I guess you really need to override the button. – manecosta May 08 '17 at 14:15
-
13For **Swift 3.1**: `override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController { // Your code... } }` – Doug Amos May 08 '17 at 14:58
-
34```viewWillDisappear(animated:)``` will get triggered if you get a phone call. This is likely not what you want. Probably better to use ```willMove(toParentViewController:)``` – Joe Susnick Nov 29 '17 at 20:46
-
in Swift 4, missing _: override func viewWillDisappear(_ animated : Bool) – Javier Calatrava Llavería Apr 24 '18 at 09:16
-
1This should be the accepted answer. Clean and simple. – temp Apr 21 '19 at 22:16
-
2No this is totally wrong This method is called when back button is press and we pop to ViewController then this method is called question is to how to do action in NavigationItem BackButton Click. This is like completion and we can do this thing in ViewWillDisappear. I hope you got what I am telling you. is there is any way to do button click then please give me solution. Thank You – Yogesh Patel Jul 01 '19 at 11:52
-
2unfortunately this also gets called when going to any moving to any new view controller... but I need to only capture the back button and nothing else – Quinn Oct 22 '19 at 14:17
-
2How has this got so much upvotes? question is rather than default behaviour need to detect when back button is tapped & do action... self.isMovingFromParent will always return true if user has navigated in this page.. – jayant rawat Mar 03 '22 at 13:47
-
Best answer, you take advantage of lifecycle view controller. – Salvatore.isc May 24 '23 at 17:36
One option would be implementing your own custom back button. You would need to add the following code to your viewDidLoad method:
- (void) viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:self action:@selector(back:)];
self.navigationItem.leftBarButtonItem = newBackButton;
}
- (void) back:(UIBarButtonItem *)sender {
// Perform your custom actions
// ...
// Go back to the previous ViewController
[self.navigationController popViewControllerAnimated:YES];
}
UPDATE:
Here is the version for Swift:
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Bordered, target: self, action: "back:")
self.navigationItem.leftBarButtonItem = newBackButton
}
@objc func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the previous ViewController
self.navigationController?.popViewControllerAnimated(true)
}
UPDATE 2:
Here is the version for Swift 3:
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
}
@objc func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the previous ViewController
_ = navigationController?.popViewController(animated: true)
}

- 2,809
- 22
- 26

- 2,249
- 1
- 12
- 12
-
2This doesn't pop to the previous view controller; it pops to the root view controller. – rocky Dec 30 '14 at 23:03
-
118
-
@rocky You may try the below line in back function: [self.navigationController dismissViewControllerAnimated:YES completion:nil]; – malajisi Jul 21 '16 at 16:32
-
2
-
7Doing a substitution of a system button to override a feature is not a good way. The best way is the answer below! http://stackoverflow.com/a/27715660/2307276 – dpizzuto Feb 01 '17 at 13:00
-
@TomSawyer I ended up using creating a new UIButton, setting the title with setTitle and then setting the image with setImage. You can see this in use with a function at the link below. Line 42. https://github.com/andrewlundy/ios-data-passing-tutorial/blob/e69cbe6ca527cd9af2bf9919a9a00cc36f9534eb/Data-Passing-Tutorial/Controller/ContactDetailsVC.swift#L42 – andrewlundy Apr 20 '19 at 03:08
-
1
override func willMove(toParent parent: UIViewController?)
{
super.willMove(toParent: parent)
if parent == nil
{
print("This VC is 'will' be popped. i.e. the back button was pressed.")
}
}
-
3Not working in Swift3/iOS10, console prints 'nested pop animation can result in corrupted navigation bar'. – itsji10dra Jan 11 '17 at 11:59
-
1
-
3This is also getting called when moving into a new VC, not just when going back. – Hedylove Jan 23 '18 at 17:00
-
As per @JozemiteApps comment, it's in the docs _Called just before the view controller is added or removed from a container view controller._. – nstein Apr 08 '18 at 14:23
-
3This should be the accepted answer. And when `parent == nil` is when we're moving *back* to the `parent` scene – Sylvan D Ash Apr 11 '18 at 13:36
-
UIViewController.willMove(toParent parent: UIViewController?) is called when the ViewController is moved to a container ViewController, it has no relation with navigation. – Guillaume Laurent Jun 29 '18 at 18:28
-
Note that this is also called when you pop programmatically, not only press back button. – User May 28 '20 at 18:16
I was able to achieve this with the following :
Swift 3
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == nil {
println("Back Button pressed.")
delegate?.goingBack()
}
}
Swift 4
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
debugPrint("Back Button pressed.")
}
}
No need of custom back button.

- 9,166
- 3
- 66
- 70

- 613
- 6
- 7
-
1This is fantastics. Old remark but still works with the latest Swift. – user3204765 Aug 09 '18 at 03:19
-
This is triggered (false positive) also when unwinding from next view controller (over this one) so not really back button press detection. – user2878850 Oct 03 '19 at 11:01
-
Same remark as previous person, this code does not detect the activation of the back button, but the pop of the current view. – Vilmir Apr 21 '20 at 21:51
If you want to have back button with back arrow you can use an image and code below
backArrow.png backArrow@2x.png
backArrow@3x.png
override func viewDidLoad() {
super.viewDidLoad()
let customBackButton = UIBarButtonItem(image: UIImage(named: "backArrow") , style: .plain, target: self, action: #selector(backAction(sender:)))
customBackButton.imageInsets = UIEdgeInsets(top: 2, left: -8, bottom: 0, right: 0)
navigationItem.leftBarButtonItem = customBackButton
}
func backAction(sender: UIBarButtonItem) {
// custom actions here
navigationController?.popViewController(animated: true)
}

- 9,763
- 4
- 55
- 62
-
-
For iOS16, SF symbol "chevron.backward" matchs the default image even more. `let image = UIImage(systemName: "chevron.backward", withConfiguration: UIImage.SymbolConfiguration(weight: .semibold))` – Sam Oct 29 '22 at 06:10
I created this (swift) class to create a back button exactly like the regular one, including back arrow. It can create a button with regular text or with an image.
Usage
weak var weakSelf = self
// Assign back button with back arrow and text (exactly like default back button)
navigationItem.leftBarButtonItems = CustomBackButton.createWithText("YourBackButtonTitle", color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))
// Assign back button with back arrow and image
navigationItem.leftBarButtonItems = CustomBackButton.createWithImage(UIImage(named: "yourImageName")!, color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))
func tappedBackButton() {
// Do your thing
self.navigationController!.popViewControllerAnimated(true)
}
CustomBackButtonClass
(code for drawing the back arrow created with Sketch & Paintcode plugin)
class CustomBackButton: NSObject {
class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImage = imageOfBackArrow(color: color)
let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.Plain, target: target, action: action)
let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.Plain , target: target, action: action)
backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), forBarMetrics: UIBarMetrics.Default)
return [negativeSpacer, backArrowButton, backTextButton]
}
class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
// recommended maximum image height 22 points (i.e. 22 @1x, 44 @2x, 66 @3x)
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
let backImageView = UIImageView(image: image)
let customBarButton = UIButton(frame: CGRectMake(0,0,22 + backImageView.frame.width,22))
backImageView.frame = CGRectMake(22, 0, backImageView.frame.width, backImageView.frame.height)
customBarButton.addSubview(backArrowImageView)
customBarButton.addSubview(backImageView)
customBarButton.addTarget(target, action: action, forControlEvents: .TouchUpInside)
return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
}
private class func drawBackArrow(frame frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
/// General Declarations
let context = UIGraphicsGetCurrentContext()!
/// Resize To Frame
CGContextSaveGState(context)
let resizedFrame = resizing.apply(rect: CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
CGContextTranslateCTM(context, resizedFrame.minX, resizedFrame.minY)
let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
CGContextScaleCTM(context, resizedScale.width, resizedScale.height)
/// Line
let line = UIBezierPath()
line.moveToPoint(CGPoint(x: 9, y: 9))
line.addLineToPoint(CGPoint.zero)
CGContextSaveGState(context)
CGContextTranslateCTM(context, 3, 11)
line.lineCapStyle = .Square
line.lineWidth = 3
color.setStroke()
line.stroke()
CGContextRestoreGState(context)
/// Line Copy
let lineCopy = UIBezierPath()
lineCopy.moveToPoint(CGPoint(x: 9, y: 0))
lineCopy.addLineToPoint(CGPoint(x: 0, y: 9))
CGContextSaveGState(context)
CGContextTranslateCTM(context, 3, 2)
lineCopy.lineCapStyle = .Square
lineCopy.lineWidth = 3
color.setStroke()
lineCopy.stroke()
CGContextRestoreGState(context)
CGContextRestoreGState(context)
}
private class func imageOfBackArrow(size size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
var image: UIImage
UIGraphicsBeginImageContextWithOptions(size, false, 0)
drawBackArrow(frame: CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
private enum ResizingBehavior {
case AspectFit /// The content is proportionally resized to fit into the target rectangle.
case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
case Stretch /// The content is stretched to match the entire target rectangle.
case Center /// The content is centered in the target rectangle, but it is NOT resized.
func apply(rect rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .AspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .AspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .Stretch:
break
case .Center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
}
SWIFT 3.0
class CustomBackButton: NSObject {
class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImage = imageOfBackArrow(color: color)
let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.plain, target: target, action: action)
let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.plain , target: target, action: action)
backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), for: UIBarMetrics.default)
return [negativeSpacer, backArrowButton, backTextButton]
}
class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
// recommended maximum image height 22 points (i.e. 22 @1x, 44 @2x, 66 @3x)
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
let backImageView = UIImageView(image: image)
let customBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 22 + backImageView.frame.width, height: 22))
backImageView.frame = CGRect(x: 22, y: 0, width: backImageView.frame.width, height: backImageView.frame.height)
customBarButton.addSubview(backArrowImageView)
customBarButton.addSubview(backImageView)
customBarButton.addTarget(target, action: action, for: .touchUpInside)
return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
}
private class func drawBackArrow(_ frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
/// General Declarations
let context = UIGraphicsGetCurrentContext()!
/// Resize To Frame
context.saveGState()
let resizedFrame = resizing.apply(CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
context.scaleBy(x: resizedScale.width, y: resizedScale.height)
/// Line
let line = UIBezierPath()
line.move(to: CGPoint(x: 9, y: 9))
line.addLine(to: CGPoint.zero)
context.saveGState()
context.translateBy(x: 3, y: 11)
line.lineCapStyle = .square
line.lineWidth = 3
color.setStroke()
line.stroke()
context.restoreGState()
/// Line Copy
let lineCopy = UIBezierPath()
lineCopy.move(to: CGPoint(x: 9, y: 0))
lineCopy.addLine(to: CGPoint(x: 0, y: 9))
context.saveGState()
context.translateBy(x: 3, y: 2)
lineCopy.lineCapStyle = .square
lineCopy.lineWidth = 3
color.setStroke()
lineCopy.stroke()
context.restoreGState()
context.restoreGState()
}
private class func imageOfBackArrow(_ size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
var image: UIImage
UIGraphicsBeginImageContextWithOptions(size, false, 0)
drawBackArrow(CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
private enum ResizingBehavior {
case AspectFit /// The content is proportionally resized to fit into the target rectangle.
case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
case Stretch /// The content is stretched to match the entire target rectangle.
case Center /// The content is centered in the target rectangle, but it is NOT resized.
func apply(_ rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .AspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .AspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .Stretch:
break
case .Center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
}
-
-
3Hi @guido, your solution is perfect, I tried your code and noticed that there is space in front of back button even though you added barbutton with negative width. – Pawriwes Aug 16 '18 at 03:48
In Swift 5 and Xcode 10.2
Please don't add custom bar button item, use this default behaviour.
No need of viewWillDisappear, no need of custom BarButtonItem etc...
It's better to detect when the VC is removed from it's parent.
Use any one of these two functions
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
callStatusDelegate?.backButtonClicked()//Here write your code
}
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
callStatusDelegate?.backButtonClicked()//Here write your code
}
}
If you want stop default behaviour of back button then add custom BarButtonItem.

- 16,698
- 6
- 112
- 113
-
6Note that this is also called when you pop programmatically, not only press back button. – User May 28 '20 at 18:16
If you are using navigationController
then add the UINavigationControllerDelegate
protocol to class and add the delegate method as follows:
class ViewController:UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController,
animated: Bool) {
if viewController === self {
// do here what you want
}
}
}
This method is called whenever the navigation controller will slide to a new screen. If the back button was pressed, the new view controller is ViewController
itself.

- 447
- 1
- 6
- 18

- 5,518
- 4
- 20
- 22
-
Which becomes horrible when using a non NSObjectProtocol class as a delegate. – Nick Weaver Sep 04 '18 at 10:27
-
You can subclass UINavigationController
and override popViewController(animated: Bool)
. Beside being able to execute some code there you can also prevent the user from going back altogether, for instance to prompt to save or discard his current work.
Sample implementation where you can set a popHandler
that gets set/cleared by pushed controllers.
class NavigationController: UINavigationController
{
var popHandler: (() -> Bool)?
override func popViewController(animated: Bool) -> UIViewController?
{
guard self.popHandler?() != false else
{
return nil
}
self.popHandler = nil
return super.popViewController(animated: animated)
}
}
And sample usage from a pushed controller that tracks unsaved work.
let hasUnsavedWork: Bool = // ...
(self.navigationController as! NavigationController).popHandler = hasUnsavedWork ?
{
// Prompt saving work here with an alert
return false // Prevent pop until as user choses to save or discard
} : nil // No unsaved work, we clear popHandler to let it pop normally
As a nice touch, this will also get called by interactivePopGestureRecognizer
when the user tries to go back using a swipe gesture.

- 10,792
- 3
- 58
- 102
NO
override func willMove(toParentViewController parent: UIViewController?) { }
This will get called even if you are segueing to the view controller in which you are overriding this method. In which check if the "parent
" is nil
of not is not a precise way to be sure of moving back to the correct UIViewController
. To determine exactly if the UINavigationController
is properly navigating back to the UIViewController
that presented this current one, you will need to conform to the UINavigationControllerDelegate
protocol.
YES
note: MyViewController
is just the name of whatever UIViewController
you want to detect going back from.
1) At the top of your file add UINavigationControllerDelegate
.
class MyViewController: UIViewController, UINavigationControllerDelegate {
2) Add a property to your class that will keep track of the UIViewController
that you are segueing from.
class MyViewController: UIViewController, UINavigationControllerDelegate {
var previousViewController:UIViewController
3) in MyViewController
's viewDidLoad
method assign self
as the delegate for your UINavigationController
.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
3) Before you segue, assign the previous UIViewController
as this property.
// In previous UIViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourSegueID" {
if let nextViewController = segue.destination as? MyViewController {
nextViewController.previousViewController = self
}
}
}
4) And conform to one method in MyViewController
of the UINavigationControllerDelegate
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController == self.previousViewController {
// You are going back
}
}

- 8,153
- 3
- 42
- 77
-
1Thanks for the helpful answer! Readers beware of setting the UINavigationController's delegate to a specific view controller; if the navigation controller already has a delegate, you run the risk of depriving that other delegate of callbacks it expects. In our app, the UINavigationController's delegate is a shared object (an AppCoordinator) that all view controllers have a pointer to. – Bill Feth Aug 23 '18 at 17:38
In my case the viewWillDisappear
worked best. But in some cases one has to modify the previous view controller. So here is my solution with access to the previous view controller and it works in Swift 4:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParentViewController {
if let viewControllers = self.navigationController?.viewControllers {
if (viewControllers.count >= 1) {
let previousViewController = viewControllers[viewControllers.count-1] as! NameOfDestinationViewController
// whatever you want to do
previousViewController.callOrModifySomething()
}
}
}
}

- 169
- 2
- 9
-
-viewDidDisappear (or -viewWillDisappear) will be called even if the view is being covered by another view controller's view (not just when the
– Bill Feth Aug 23 '18 at 17:30
Before leave current controller I need to show alert. So I did it this way:
- Add extention to
UINavigationController
withUINavigationBarDelegate
- Add selector to your controller navigationShouldPopOnBack(completion:)
It's worked)
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let items = navigationBar.items, viewControllers.count < items.count {
return true
}
let clientInfoVC = topViewController as? ClientInfoVC
if clientInfoVC?.responds(to: #selector(clientInfoVC?.navigationShouldPopOnBack)) ?? false {
clientInfoVC?.navigationShouldPopOnBack(completion: { isAllowPop in
if isAllowPop {
DispatchQueue.main.async {
self.popViewController(animated: true)
}
}
})
}
DispatchQueue.main.async {
self.popViewController(animated: true)
}
return false
}
}
@objc func navigationShouldPopOnBack(completion: @escaping (Bool) -> ()) {
let ok = UIAlertAction(title: R.string.alert.actionOk(), style: .default) { _ in
completion(true)
}
let cancel = UIAlertAction(title: R.string.alert.actionCancel(), style: .cancel) { _ in
completion(false)
}
let alertController = UIAlertController(title: "", message: R.string.alert.contractMessage(), preferredStyle: .alert)
alertController.addAction(ok)
alertController.addAction(cancel)
present(alertController, animated: true, completion: nil)
}
-
Hi @mgyky/@Taras, any clue why the navigationShouldPopOnBack is no more called from my side? Does iOS12 could be the issue? – David Nov 20 '20 at 18:55
-
Hi @David. In my side everything is ok in iOS 12. Can you send your code? – Taras Nov 23 '20 at 08:56
-
Hi @Taras, any reason why you did not move to iOS 13 or 14 yet? Maybe your device can't? If you can, please try to use the latest iOS to see the behavior. My original code was this https://gist.github.com/HamGuy/a099058e674b573ffe433132f7b5651e and I also tried this https://www.programmersought.com/article/1529883806/ and – David Nov 23 '20 at 14:43
When back button is pressed, ignore interactive pop with screen edge gesture.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent, transitionCoordinator?.isInteractive == false {
// code here
}
}

- 1,023
- 1
- 9
- 18
It's not difficult as we thing. Just create a frame for UIButton with clear background color, assign action for the button and place over the navigationbar back button. And finally remove the button after use.
Here is the Swift 3 sample code done with UIImage instead of UIButton
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
imageView.backgroundColor = UIColor.clear
imageView.frame = CGRect(x:0,y:0,width:2*(self.navigationController?.navigationBar.bounds.height)!,height:(self.navigationController?.navigationBar.bounds.height)!)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(back(sender:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.tag = 1
self.navigationController?.navigationBar.addSubview(imageView)
}
write the code need to be executed
func back(sender: UIBarButtonItem) {
// Perform your custom actions}
_ = self.navigationController?.popViewController(animated: true)
}
Remove the subView after action is performed
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
for view in (self.navigationController?.navigationBar.subviews)!{
if view.tag == 1 {
view.removeFromSuperview()
}
}

- 165
- 2
- 7
-
-
-
This doesn't appear to work in iOS 11. Not when the UIImageView's background colour is clear. Set it to a different colour and it works. – Tap Forms Jun 29 '18 at 07:43
-
We can define a UIImageView with clear color, set its frame, assign tapgesture and place anywhere in the screen. Then why can't we place it over a navigation bar. To be sincere I won't recommend what I wrote . If there is an issue definitely there is a reason but it is not the color matters. Forget the code follow the logic u will succeed. :) – ARSHWIN DENUEV LAL Jul 19 '18 at 16:42
This is my solution
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let shouldBlock = self.topViewController?.shouldPopFromNavigation() {
return shouldBlock
}
return true
}
}
extension UIViewController {
@objc func shouldPopFromNavigation() -> Bool {
return true
}
}
In your view controller, you can handle like this:
@objc override func shouldPopFromNavigation() -> Bool {
// Your dialog, example UIAlertViewController or whatever you want
return false
}

- 391
- 5
- 10
-
Hi @Hiro, any clue why the shouldPopFromNavigation is no more called from my side? Does iOS12 could be the issue? – David Nov 20 '20 at 18:55
-
@David: It should work for all. How about the status now? Did you put shouldPopFromNavigation in extension for UIViewController? – Hiro Dec 02 '20 at 04:28
-
I tried the same code on my iphone 5 running iOS 12 and it worked. The same code on my XR running iOS 14, does not. – David Dec 02 '20 at 20:53
-
I use `navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem)` instead of `shouldPop` and works great fro force large title on the previous screen, thanks! – Maetschl Aug 13 '21 at 15:39
Swift 4.2:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
// Your code...
}
}

- 605
- 1
- 6
- 19
For Swift 5, we can check it in view will disappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
delegate?.passValue(clickedImage: selectedImage)
}
}

- 436
- 4
- 8
Swift 5 __ Xcode 11.5
In my case I wanted to make an animation, and when it finished, go back. A way to overwrite the default action of the back button and call your custom action is this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setBtnBack()
}
private func setBtnBack() {
for vw in navigationController?.navigationBar.subviews ?? [] where "\(vw.classForCoder)" == "_UINavigationBarContentView" {
print("\(vw.classForCoder)")
for subVw in vw.subviews where "\(subVw.classForCoder)" == "_UIButtonBarButton" {
let ctrl = subVw as! UIControl
ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
}
}
}
@objc func backBarBtnAction() {
doSomethingBeforeBack { [weak self](isEndedOk) in
if isEndedOk {
self?.navigationController?.popViewController(animated: true)
}
}
}
private func doSomethingBeforeBack(completion: @escaping (_ isEndedOk:Bool)->Void ) {
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.vwTxt.alpha = 0
}) { (isEnded) in
completion(isEnded)
}
}
Or you can use this method one time to explore the NavigationBar view hierarchy, and get the indexes to access to the _UIButtonBarButton view, cast to UIControl, remove the target-action, and add your custom targets-actions:
private func debug_printSubviews(arrSubviews:[UIView]?, level:Int) {
for (i,subVw) in (arrSubviews ?? []).enumerated() {
var str = ""
for _ in 0...level {
str += "\t"
}
str += String(format: "%2d %@",i, "\(subVw.classForCoder)")
print(str)
debug_printSubviews(arrSubviews: subVw.subviews, level: level + 1)
}
}
// Set directly the indexs
private func setBtnBack_method2() {
// Remove or comment the print lines
debug_printSubviews(arrSubviews: navigationController?.navigationBar.subviews, level: 0)
let ctrl = navigationController?.navigationBar.subviews[1].subviews[0] as! UIControl
print("ctrl.allTargets: \(ctrl.allTargets)")
ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
print("ctrl.allTargets: \(ctrl.allTargets)")
ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
print("ctrl.allTargets: \(ctrl.allTargets)")
}

- 427
- 4
- 7
-
1This works as expected, but in iOS 15 when I tap and hold the back button it pops up the menu actions and executes the back action. – Abdul Momen Dec 09 '21 at 07:44
-
Clever but you are REALLY playing with fire/begging for trouble to create solutions that dig that far into the undocumented implementation! It could change at any time, any update, release and break your app. Almost every time I try to get super clever and complicated and go under the hood to solve a problem it's not worth it, and there's usually a better design under the circumstances that I'm stubbornly refusing to see and appreciate. – clearlight May 04 '22 at 01:56
Swift 3:
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
if parent == nil{
print("Back button was clicked")
}
}

- 1,582
- 2
- 14
- 30
-
-did/willMove(toParentViewController:) is possibly better than checking isMovingTfromParentViewController in -viewWillDisappear since it is only called when the view controller is actually changing parents (not when the view is covered by another VC's view) But the more "correct" solution is to implement the UINavigationController delegate method. Be careful, though; if the NavigationController already has a delegate, you run the risk of depriving that other delegate of callbacks it expects. – Bill Feth Aug 23 '18 at 17:35
-
I tested with a splitViewController. There, could not make the difference between added or removed. – claude31 Dec 28 '19 at 14:06
just do control + drag the bar item to below func. work like charm
@IBAction func done(sender: AnyObject) {
if((self.presentingViewController) != nil){
self.dismiss(animated: false, completion: nil)
print("done")
}
}

- 2,287
- 1
- 12
- 20
override public func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.topItem?.title = GlobalVariables.selectedMainIconName
let image = UIImage(named: "back-btn")
image = image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: UIBarButtonItemStyle.Plain, target: self, action: #selector(Current[enter image description here][1]ViewController.back) )
}
func back() {
self.navigationController?.popToViewController( self.navigationController!.viewControllers[ self.navigationController!.viewControllers.count - 2 ], animated: true)
}

- 31
- 1
- 3
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingToParent {
//your code backView
}
}

- 576
- 4
- 11
Try this .
self.navigationItem.leftBarButtonItem?.target = "methodname"
func methodname ( ) {
// enter code here
}
Try on this too.
override func viewWillAppear(animated: Bool) {
//empty your array
}

- 8,021
- 7
- 61
- 120

- 14,148
- 92
- 64
-
Doesn't work. self.navigationItem.leftBarButtonItem is nil when using the default (iOS provided) back button, which @StevenZ appears to be asking about. – Smartcat Jan 04 '23 at 21:50
As I understand you want to empty your array
as you press your back button and pop to your previous ViewController let
your Array
which you loaded on this screen is
let settingArray = NSMutableArray()
@IBAction func Back(sender: AnyObject) {
self. settingArray.removeAllObjects()
self.dismissViewControllerAnimated(true, completion: nil)
}

- 1,851
- 16
- 19

- 797
- 9
- 19
Here is the simplest possible Swift 5 solution that doesn't require you to create a custom back button and give up all that UINavigationController left button functionality you get for free.
As Brandon A recommends above, you need need to implement UINavigationControllerDelegate
in the view controller you want to interact with before returning to it. A good way is to create an unwind segue that you can perform manually or automatically and reuse the same code from a custom done button or the back button.
First, make your view controller of interest (the one you want to detect returning to) a delegate of the navigation controller in its viewDidLoad
:
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
Second, add an extension at the bottom of the file that overrides navigationController(willShow:animated:)
extension PickerTableViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool) {
if let _ = viewController as? EditComicBookViewController {
let selectedItemRow = itemList.firstIndex(of: selectedItemName)
selectedItemIndex = IndexPath(row: selectedItemRow!, section: 0)
if let selectedCell = tableView.cellForRow(at: selectedItemIndex) {
performSegue(withIdentifier: "PickedItem", sender: selectedCell)
}
}
}
}
Since your question included a UITableViewController
, I included a way to get the index path of the row the user tapped.

- 5,366
- 2
- 14
- 16
My preference was to override the popViewController in the Navigation Controller. The advantages of this is:
- Your app keeps the default Back Button look and animations, and you don't have to manage it. This is particularly helpful if a user has Large Text set on their phone, since the default back button will increase or decrease in size based on the user settings.
- You can stop the view from popping altogether, unlike using viewWillDisappear.
First, create a custom Navigation Controller class (and be sure to assign it to the Navigation Controller in your Story Board or wherever your navigation controller is created):
class NavControllerWithBackButtonOverride: UINavigationController {
var backButtonOverride: (() -> Void)? = nil
override func popViewController(animated: Bool) -> UIViewController? {
if backButtonOverride != nil {
//if anything is assigned to the backButtonOverride the override will run
self.backButtonOverride!()
return nil
} else {
//otherwise the default popViewController will run
return super.popViewController(animated: animated)
}
}
}
Then enable/disable the override in your View Controller by assigning a value to the backButtonOverride variable:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.enableCustomBackButton()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.disableCustomBackButton()
}
/**
Custom Back Button
*/
func customBackButtonAction() {
print("DO THIS INSTEAD")
}
func enableCustomBackButton() {
if let nav = self.navigationController as? NavControllerWithBackButtonOverride {
nav.backButtonOverride = { self.customBackButtonAction() }
nav.interactivePopGestureRecognizer?.isEnabled = false
}
}
func disableCustomBackButton() {
if let nav = self.navigationController as? NavControllerWithBackButtonOverride {
nav.backButtonOverride = nil
nav.interactivePopGestureRecognizer?.isEnabled = true
}
}
Note: I also disabled interactivePopGestureRecognizer because it was causing issues with the custom setup.

- 754
- 7
- 13
I accomplished this by calling/overriding viewWillDisappear
and then accessing the stack of the navigationController
like this:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let stack = self.navigationController?.viewControllers.count
if stack >= 2 {
// for whatever reason, the last item on the stack is the TaskBuilderViewController (not self), so we only use -1 to access it
if let lastitem = self.navigationController?.viewControllers[stack! - 1] as? theViewControllerYoureTryingToAccess {
// hand over the data via public property or call a public method of theViewControllerYoureTryingToAccess, like
lastitem.emptyArray()
lastitem.value = 5
}
}
}

- 520
- 5
- 15
You can do something in your Viewcontroller like
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction() //Your action you want to perform.
return true
}
For complete answer use Detecting when the 'back' button is pressed on a navbar

- 4,304
- 1
- 23
- 18
Swift 5+ (Back button with alert control)
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "<Back", style: UIBarButtonItem.Style.plain, target: self, action: #selector(PGWebViewController.back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
}
@objc func back(sender: UIBarButtonItem) {
let alert = UIAlertController(title: "Warning!", message: "Your payment process is not completed yet. Do you want to go back?", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { action in
_ = self.navigationController?.popViewController(animated: true)
})
alert.addAction(ok)
let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
})
alert.addAction(cancel)
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})}

- 247
- 3
- 13
Swift 5+ Xcode 14+
I tried multiple ways to override default back button by appending and removing view controllers from navigation stack.
But below solution worked only.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: #selector(popToRoot))
}
@objc func popToRoot() {
self.navigationController?.popToRootViewController(animated: true)
}

- 4,256
- 1
- 26
- 30
You can simply remove unnecessary controllers from the stack, something like this:
self.navigationController?.viewControllers.removeAll(where: {
$0 is FirstViewController || $0 is SecondWithPinController
})

- 41
- 5
This is how I solved it for my own problem
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.leftBarButtonItem?.action = #selector(self.back(sender:))
self.navigationItem.leftBarButtonItem?.target = self
}
@objc func back(sender: UIBarButtonItem) {
}

- 10,760
- 10
- 47
- 69