iOS - Hide status bar on a click of a button - ios

I got two buttons, show and hide button and I want to hide the status bar when the button hide is clicked I tried this method but it didn't work
- (IBAction)hide:(id)sender {
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:
UIStatusBarAnimationSlide];
}
Is there is any way to do what I want?

you can simply set it's delegate :-
- (BOOL)prefersStatusBarHidden
{
return YES;
}
and called this method in to your method [self setNeedsStatusBarAppearanceUpdate];

In your app's plist file add a row call it "View controller-based status bar appearance" and set it to NO.

For Swift 3 and iOS10
This is how I'm using it with AVPlayer (not part of a question but on click is the same) with fullscreen support
private var fullscreenEnabled: Bool = false
func videoFullscreenButtonClicked(_ sender: UIButton!) {
if avPlayerLayer.videoGravity == AVLayerVideoGravityResizeAspectFill {
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect
fullscreenEnabled = false
setNeedsStatusBarAppearanceUpdate()
} else {
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
fullscreenEnabled = true
setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool {
return fullscreenEnabled
}

Related

How to detect current orientation of a view controller iOS?

In my application manually sets the orientation according to view controller. Besides this view controllers, I need to detect current orientation of the most top view controller(Not the Device orientation). Is it possible?
This is how I set the orientation.
-(BOOL)shouldAutorotate {
return YES;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
As said, you want to check the current orientation set in your root/ top most VC.
So you can detect with this code:-
if (topViewController.responds(to: Selector(("canRotate")))) else{
// view is set to portrait
return .portrait;
}
// // view is set to landscape
return .allButUpsideDown;
You can set orientation of each view controller by:
class YourViewController: UIViewController {
var _orientations = UIInterfaceOrientationMask.landscape
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
get { return self._orientations }
set { self._orientations = newValue }
}
override func viewDidLoad() {
super.viewDidLoad()
}
//...
}
and then read this property everywhere you want like:
let vc = YourViewController()
print(vc._orientation)

set status bar?

How to hide, show or change the status bar dynamically on events such as click or value change?
I've googled for a well, but all I get is changed from the beginning as this. Here I need to do it dynamically.
Well for this you can do following based on events:
//For hiding/unhiding:
func hideStatusBar(shouldHide:Bool){
UIApplication.shared.isStatusBarHidden = shouldHide
}
//For Light Style:
func lightStatusBar(){
UIApplication.shared.statusBarStyle = .lightContent
}
//For Standard Style:
func standardStatusBar(){
UIApplication.shared.statusBarStyle = .default
}
or you can tweak prefersStatusBarHidden: as well.
UIApplication.sharedApplication().setStatusBarHidden=true
You are looking for var prefersStatusBarHidden: Bool { get }
Override that function in your view controller. Dynamically update the visibility of your statusbar with func setNeedsStatusBarAppearanceUpdate()
https://developer.apple.com/reference/uikit/uiviewcontroller/1621440-prefersstatusbarhidden

Hide status bar on button tap Swift 3 / iOS 10

I cannot seem to be able to hide the status bar in my root view controller (A) when a button is pressed.
I have set the properties in my info.plist Status bar is initially hidden and View controller-based status bar appearance to YES.
If I implement the override var prefersStatusBarHidden: Bool { get }, the status bar will be definitively hidden (or not).
What I need
I want the status bar to be displayed in (A) but hidden when I press a button that adds a child view controller to (A).
I have tried setting prefersStatusBarHidden to false and hide it using UIApplication.shared.isStatusBarHidden = true when the button is pressed but this does not work.
There must be something I am getting wrong, could anyone enlighten me ?
Thanks in advance.
PS: I merely need it to be invisible, not necessarily "hidden" in the Swift sense.
To hide the status bar
UIApplication.shared.keyWindow?.windowLevel = UIWindowLevelStatusBar
To bring back the status bar
UIApplication.shared.keyWindow?.windowLevel = UIWindowLevelNormal
You can create a variable to hold a boolean to set if the status bar is hidden or not
var _isStatusBarHidden: Bool = false
Then create its getter and setter
var isStatusBarHidden: Bool {
get{
return _isStatusBarHidden
}
set {
if _isStatusBarHidden != newValue {
_isStatusBarHidden = newValue
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
You must override the prefersStatusBarHidden property
override var prefersStatusBarHidden: Bool {
return self._isStatusBarHidden
}
Last thing is to set the variable to true when the user presses the button:
self.isStatusBarHidden = true
Just a little improvement of #Alexandre Lara `s code:
var isStatusBarHidden: Bool = false {
didSet {
if oldValue != self.isStatusBarHidden {
self.setNeedsStatusBarAppearanceUpdate()
}
}
}
override var prefersStatusBarHidden: Bool {
return self.isStatusBarHidden
}

Using prefersStatusBarHidden without loosing default statusbar visibility behavior (of hiding statusbar when on vertically compact environment)

I have prefersStatusBarHidden dynamic return value, it does show/hide the statusbar, Except of one problem:
When prefersStatusBarHidden return value is false, the statusbar is shown no matter if we are on a vertically compact environment (which is not the default behaviour when not using prefersStatusBarHidden).
Is there any way to use prefersStatusBarHidden still be able to detect if we are on vertically compact environment and hide the statusbar.
My current code looks something like below:
class SomeViewController: UIViewController {
var someStatusValue = false
override var prefersStatusBarHidden : Bool {
guard (someStatusValue) else { return true }
// Here instead of returning `false`, if we are on VERTICALLY COMPACT ENVIRONMENT return `true` else `false`
return false
}
}
Additional: I am using UINavigationController based viewcontroller, and View controller-based status bar appearance: YES in info.plist
I just finally figured it out
override var prefersStatusBarHidden : Bool {
guard (someStatusValue) else { return true }
if (UIDevice.current.userInterfaceIdiom == .phone && UIDeviceOrientationIsLandscape(UIDevice.current.orientation)) {
return true
} else {
return false
}
}
I also have following line to register for orientation change in viewWillAppear
NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged(_:)), name: .UIDeviceOrientationDidChange, object: nil)
And the function which is responsible for recieving the notification and updating the statusbar:
func orientationChanged(_ note: Notification) {
self.setNeedsStatusBarAppearanceUpdate()
}
Note: you must NotificationCenter.default.removeObserver to unregister for orientation notifications in viewWillDisappear

Setting action for back button in navigation controller

I'm trying to overwrite the default action of the back button in a navigation controller. I've provided a target an action on the custom button. The odd thing is when assigning it though the backbutton attribute it doesn't pay attention to them and it just pops the current view and goes back to the root:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc]
initWithTitle: #"Servers"
style:UIBarButtonItemStylePlain
target:self
action:#selector(home)];
self.navigationItem.backBarButtonItem = backButton;
As soon as I set it through the leftBarButtonItem on the navigationItem it calls my action, however then the button looks like a plain round one instead of the arrowed back one:
self.navigationItem.leftBarButtonItem = backButton;
How can I get it to call my custom action before going back to the root view? Is there a way to overwrite the default back action, or is there a method that is always called when leaving a view (viewDidUnload doesn't do that)?
Try putting this into the view controller where you want to detect the press:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
I've implemented UIViewController-BackButtonHandler extension. It does not need to subclass anything, just put it into your project and override navigationShouldPopOnBackButton method in UIViewController class:
-(BOOL) navigationShouldPopOnBackButton {
if(needsShowConfirmation) {
// Show confirmation alert
// ...
return NO; // Ignore 'Back' button this time
}
return YES; // Process 'Back' button click and pop view controller
}
Download sample app.
Unlike Amagrammer said, it's possible. You have to subclass your navigationController. I explained everything here (including example code).
Swift Version:
(of https://stackoverflow.com/a/19132881/826435)
In your view controller you just conform to a protocol and perform whatever action you need:
extension MyViewController: NavigationControllerBackButtonDelegate {
func shouldPopOnBackButtonPress() -> Bool {
performSomeActionOnThePressOfABackButton()
return false
}
}
Then create a class, say NavigationController+BackButton, and just copy-paste the code below:
protocol NavigationControllerBackButtonDelegate {
func shouldPopOnBackButtonPress() -> Bool
}
extension UINavigationController {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
// Prevents from a synchronization issue of popping too many navigation items
// and not enough view controllers or viceversa from unusual tapping
if viewControllers.count < navigationBar.items!.count {
return true
}
// Check if we have a view controller that wants to respond to being popped
var shouldPop = true
if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
shouldPop = viewController.shouldPopOnBackButtonPress()
}
if (shouldPop) {
DispatchQueue.main.async {
self.popViewController(animated: true)
}
} else {
// Prevent the back button from staying in an disabled state
for view in navigationBar.subviews {
if view.alpha < 1.0 {
UIView.animate(withDuration: 0.25, animations: {
view.alpha = 1.0
})
}
}
}
return false
}
}
It isn't possible to do directly. There are a couple alternatives:
Create your own custom UIBarButtonItem that validates on tap and pops if the test passes
Validate the form field contents using a UITextField delegate method, such as -textFieldShouldReturn:, which is called after the Return or Done button is pressed on the keyboard
The downside of the first option is that the left-pointing-arrow style of the back button cannot be accessed from a custom bar button. So you have to use an image or go with a regular style button.
The second option is nice because you get the text field back in the delegate method, so you can target your validation logic to the specific text field sent to the delegate call-back method.
For some threading reasons, the solution mentionned by #HansPinckaers wasn't right for me, but I found a very easier way to catch a touch on the back button, and I wanna pin this down here in case this could avoid hours of deceptions for someone else.
The trick is really easy : just add a transparent UIButton as a subview to your UINavigationBar, and set your selectors for him as if it was the real button!
Here's an example using Monotouch and C#, but the translation to objective-c shouldn't be too hard to find.
public class Test : UIViewController {
public override void ViewDidLoad() {
UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
b.BackgroundColor = UIColor.Clear; //making the background invisible
b.Title = string.Empty; // and no need to write anything
b.TouchDown += delegate {
Console.WriteLine("caught!");
if (true) // check what you want here
NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
};
NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
}
}
Fun fact : for testing purposes and to find good dimensions for my fake button, I set its background color to blue... And it shows behind the back button! Anyway, it still catches any touch targetting the original button.
Overriding navigationBar(_ navigationBar:shouldPop): This is not a good idea, even if it works. for me it generated random crashes on navigating back. I advise you to just override the back button by removing the default backButton from navigationItem and creating a custom back button like below:
override func viewDidLoad(){
super.viewDidLoad()
navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction)
...
}
========================================
Building on previous responses with UIAlert in Swift5 in a Asynchronous way
protocol NavigationControllerBackButtonDelegate {
func shouldPopOnBackButtonPress(_ completion: #escaping (Bool) -> ())
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if viewControllers.count < navigationBar.items!.count {
return true
}
// Check if we have a view controller that wants to respond to being popped
if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
viewController.shouldPopOnBackButtonPress { shouldPop in
if (shouldPop) {
/// on confirm => pop
DispatchQueue.main.async {
self.popViewController(animated: true)
}
} else {
/// on cancel => do nothing
}
}
/// return false => so navigator will cancel the popBack
/// until user confirm or cancel
return false
}else{
DispatchQueue.main.async {
self.popViewController(animated: true)
}
}
return true
}
}
On your controller
extension MyController: NavigationControllerBackButtonDelegate {
func shouldPopOnBackButtonPress(_ completion: #escaping (Bool) -> ()) {
let msg = "message"
/// show UIAlert
alertAttention(msg: msg, actions: [
.init(title: "Continuer", style: .destructive, handler: { _ in
completion(true)
}),
.init(title: "Annuler", style: .cancel, handler: { _ in
completion(false)
})
])
}
}
This technique allows you to change the text of the "back" button without affecting the title of any of the view controllers or seeing the back button text change during the animation.
Add this to the init method in the calling view controller:
UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];
temporaryBarButtonItem.title = #"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];
Easiest way
You can use the UINavigationController's delegate methods. The method willShowViewController is called when the back button of your VC is pressed.do whatever you want when back btn pressed
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
Here's my Swift solution. In your subclass of UIViewController, override the navigationShouldPopOnBackButton method.
extension UIViewController {
func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController {
func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
if let vc = self.topViewController {
if vc.navigationShouldPopOnBackButton() {
self.popViewControllerAnimated(true)
} else {
for it in navigationBar.subviews {
let view = it as! UIView
if view.alpha < 1.0 {
[UIView .animateWithDuration(0.25, animations: { () -> Void in
view.alpha = 1.0
})]
}
}
return false
}
}
return true
}
}
Found a solution which retains the back button style as well.
Add the following method to your view controller.
-(void) overrideBack{
UIButton *transparentButton = [[UIButton alloc] init];
[transparentButton setFrame:CGRectMake(0,0, 50, 40)];
[transparentButton setBackgroundColor:[UIColor clearColor]];
[transparentButton addTarget:self action:#selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
[self.navigationController.navigationBar addSubview:transparentButton];
}
Now provide a functionality as needed in the following method:
-(void)backAction:(UIBarButtonItem *)sender {
//Your functionality
}
All it does is to cover the back button with a transparent button ;)
I don't believe this is possible, easily. The only way I believe to get around this is to make your own back button arrow image to place up there. It was frustrating for me at first but I see why, for consistency's sake, it was left out.
You can get close (without the arrow) by creating a regular button and hiding the default back button:
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
There's an easier way by just subclassing the delegate method of the UINavigationBar and override the ShouldPopItemmethod.
This approach worked for me (but the "Back" button will not have the "<" sign):
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(backButtonClicked)];
self.navigationItem.leftBarButtonItem = backNavButton;
}
-(void)backButtonClicked
{
// Do something...
AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[delegate.navController popViewControllerAnimated:YES];
}
onegray's solution is not safe.According to the official documents by Apple,https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html,
we should avoid doing that.
"If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes."
Using Swift:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
Here is Swift 3 version of #oneway's answer for catching navigation bar back button event before it gets fired. As UINavigationBarDelegate cannot be used for UIViewController, you need to create a delegate that will be triggered when navigationBar shouldPop is called.
#objc public protocol BackButtonDelegate {
#objc optional func navigationShouldPopOnBackButton() -> Bool
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if viewControllers.count < (navigationBar.items?.count)! {
return true
}
var shouldPop = true
let vc = self.topViewController
if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
shouldPop = vc.navigationShouldPopOnBackButton()
}
if shouldPop {
DispatchQueue.main.async {
self.popViewController(animated: true)
}
} else {
for subView in navigationBar.subviews {
if(0 < subView.alpha && subView.alpha < 1) {
UIView.animate(withDuration: 0.25, animations: {
subView.alpha = 1
})
}
}
}
return false
}
}
And then, in your view controller add the delegate function:
class BaseVC: UIViewController, BackButtonDelegate {
func navigationShouldPopOnBackButton() -> Bool {
if ... {
return true
} else {
return false
}
}
}
I've realised that we often want to add an alert controller for users to decide whether they wanna go back. If so, you can always return false in navigationShouldPopOnBackButton() function and close your view controller by doing something like this:
func navigationShouldPopOnBackButton() -> Bool {
let alert = UIAlertController(title: "Warning",
message: "Do you want to quit?",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
present(alert, animated: true, completion: nil)
return false
}
func yes() {
print("yes")
DispatchQueue.main.async {
_ = self.navigationController?.popViewController(animated: true)
}
}
func no() {
print("no")
}
Swift 4 iOS 11.3 Version:
This builds on the answer from kgaidis from https://stackoverflow.com/a/34343418/4316579
I am not sure when the extension stopped working, but at the time of this writing (Swift 4), it appears that the extension will no longer be executed unless you declare UINavigationBarDelegate conformity as described below.
Hope this helps people that are wondering why their extension no longer works.
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
}
}
By using the target and action variables that you are currently leaving 'nil', you should be able to wire your save-dialogs in so that they are called when the button is "selected". Watch out, this may get triggered at strange moments.
I agree mostly with Amagrammer, but I don't think it would be that hard to make the button with the arrow custom. I would just rename the back button, take a screen shot, photoshop the button size needed, and have that be the image on the top of your button.
You can try accessing the NavigationBars Right Button item and set its selector property...heres a reference UIBarButtonItem reference, another thing if this doenst work that will def work is, set the right button item of the nav bar to a custom UIBarButtonItem that you create and set its selector...hope this helps
For a form that requires user input like this, I would recommend invoking it as a "modal" instead of part of your navigation stack. That way they have to take care of business on the form, then you can validate it and dismiss it using a custom button. You can even design a nav bar that looks the same as the rest of your app but gives you more control.
To intercept the Back button, simply cover it with a transparent UIControl and intercept the touches.
#interface MyViewController : UIViewController
{
UIControl *backCover;
BOOL inhibitBackButtonBOOL;
}
#end
#implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Cover the back button (cannot do this in viewWillAppear -- too soon)
if ( backCover == nil ) {
backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
// show the cover for testing
backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
[backCover addTarget:self action:#selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
UINavigationBar *navBar = self.navigationController.navigationBar;
[navBar addSubview:backCover];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[backCover removeFromSuperview];
backCover = nil;
}
- (void)backCoverAction
{
if ( inhibitBackButtonBOOL ) {
NSLog(#"Back button aborted");
// notify the user why...
} else {
[self.navigationController popViewControllerAnimated:YES]; // "Back"
}
}
#end
At least in Xcode 5, there is a simple and pretty good (not perfect) solution. In IB, drag a Bar Button Item off the Utilities pane and drop it on the left side of the Navigation Bar where the Back button would be. Set the label to "Back." You will have a functioning button that you can tie to your IBAction and close your viewController. I'm doing some work and then triggering an unwind segue and it works perfectly.
What isn't ideal is that this button does not get the < arrow and does not carry forward the previous VCs title, but I think this can be managed. For my purposes, I set the new Back button to be a "Done" button so it's purpose is clear.
You also end up with two Back buttons in the IB navigator, but it is easy enough to label it for clarity.
Swift
override func viewWillDisappear(animated: Bool) {
let viewControllers = self.navigationController?.viewControllers!
if indexOfArray(viewControllers!, searchObject: self) == nil {
// do something
}
super.viewWillDisappear(animated)
}
func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
for (index, value) in enumerate(array) {
if value as UIViewController == searchObject as UIViewController {
return index
}
}
return nil
}
Found new way to do it :
Objective-C
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == NULL) {
NSLog(#"Back Pressed");
}
}
Swift
override func didMoveToParentViewController(parent: UIViewController?) {
if parent == nil {
println("Back Pressed")
}
}
Swift version of #onegray's answer
protocol RequestsNavigationPopVerification {
var confirmationTitle: String { get }
var confirmationMessage: String { get }
}
extension RequestsNavigationPopVerification where Self: UIViewController {
var confirmationTitle: String {
return "Go back?"
}
var confirmationMessage: String {
return "Are you sure?"
}
}
final class NavigationController: UINavigationController {
func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
popViewControllerAnimated(true)
return true
}
let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
dispatch_async(dispatch_get_main_queue(), {
let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
UIView.animateWithDuration(0.25) {
dimmed.forEach { $0.alpha = 1 }
}
})
return
})
alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
dispatch_async(dispatch_get_main_queue(), {
self.popViewControllerAnimated(true)
})
})
presentViewController(alertController, animated: true, completion: nil)
return false
}
}
Now in any controller, just conform to RequestsNavigationPopVerification and this behaviour is adopted by default.
Use isMovingFromParentViewController
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(true)
if self.isMovingFromParentViewController {
// current viewController is removed from parent
// do some work
}
}
The answer from #William is correct however, if the user starts a swipe-to-go-back gesture the viewWillDisappear method is called and even self won't be in the navigation stack (that is, self.navigationController.viewControllers won't contain self), even if the swipe is not completed and the view controller is not actually popped. Thus, the solution would be to:
Disable the swipe-to-go-back gesture in viewDidAppear and only allow using the back button, by using:
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
Or simply use viewDidDisappear instead, as follows:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (![self.navigationController.viewControllers containsObject:self])
{
// back button was pressed or the the swipe-to-go-back gesture was
// completed. We know this is true because self is no longer
// in the navigation stack.
}
}
The solution I have found so far is not very nice, but it works for me. Taking this answer, I also check whether I'm popping programmatically or not:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
You have to add that property to your controller and set it to YES before popping programmatically:
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

Resources