Detecting when the 'back' button is pressed on a navbar
Solution 1:
UPDATE: According to some comments, the solution in the original answer does not seem to work under certain scenarios in iOS 8+. I can't verify that that is actually the case without further details.
For those of you however in that situation there's an alternative. Detecting when a view controller is being popped is possible by overriding willMove(toParentViewController:)
. The basic idea is that a view controller is being popped when parent
is nil
.
Check out "Implementing a Container View Controller" for further details.
Since iOS 5 I've found that the easiest way of dealing with this situation is using the new method - (BOOL)isMovingFromParentViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
makes sense when you are pushing and popping controllers in a navigation stack.
However, if you are presenting modal view controllers you should use - (BOOL)isBeingDismissed
instead:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
As noted in this question, you could combine both properties:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
Other solutions rely on the existence of a UINavigationBar
. Instead like my approach more because it decouples the required tasks to perform from the action that triggered the event, i.e. pressing a back button.
Solution 2:
While viewWillAppear()
and viewDidDisappear()
are called when the back button is tapped, they are also called at other times. See end of answer for more on that.
Using UIViewController.parent
Detecting the back button is better done when the VC is removed from it's parent (the NavigationController) with the help of willMoveToParentViewController(_:)
OR didMoveToParentViewController()
If parent is nil, the view controller is being popped off the navigation stack and dismissed. If parent is not nil, it is being added to the stack and presented.
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
Swap out willMove
for didMove
and check self.parent to do work after the view controller is dismissed.
Stopping the dismiss
Do note, checking the parent doesn't allow you to "pause" the transition if you need to do some sort of async save. To do that you could implement the following. Only downside here is you lose the fancy iOS styled/animated back button. Also be careful here with the interactive swipe gesture. Use the following to handle this case.
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
More on view will/did appear
If you didn't get the viewWillAppear
viewDidDisappear
issue, Let's run through an example. Say you have three view controllers:
- ListVC: A table view of things
- DetailVC: Details about a thing
- SettingsVC: Some options for a thing
Lets follow the calls on the detailVC
as you go from the listVC
to settingsVC
and back to listVC
List > Detail (push detailVC) Detail.viewDidAppear
<- appear
Detail > Settings (push settingsVC) Detail.viewDidDisappear
<- disappear
And as we go back...
Settings > Detail (pop settingsVC) Detail.viewDidAppear
<- appear
Detail > List (pop detailVC) Detail.viewDidDisappear
<- disappear
Notice that viewDidDisappear
is called multiple times, not only when going back, but also when going forward. For a quick operation that may be desired, but for a more complex operation like a network call to save, it may not.