Abort navigation back if unsaved changes (ios7) - ios

I want to prompt the user to save changes before they navigate back out of editing an entity in my IOS7 application.
An early stack overflow said I can detect this will happen at this point here: but I've been unable to abort the navigation.
-(void)willMoveToParentViewController:(UIViewController *)parent {
NSLog(#"\t\t\tThis VC has has been pushed popped OR covered");
if (!parent) {
NSLog(#"\t\t\tThis happens ONLY when it's popped");
}
}
basically if my managedObjectContext has unsaved changes I'd like to pop an alert box before the back navigation event happens. Any ideas?
I tried this solution: https://stackoverflow.com/a/19210888/2069812
but doesn't seem to work.

You can use custom back button and handle its touch event yourself. It's the simplest solution in this case.
For example:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Button" style: UIBarButtonItemStylePlain target:self action:#selector(buttonDidTouch:)];
- (void)buttonDidTouch:(id)sender)
{
if (edited) {
[self.navigationController popViewControllerAnimated:YES];
} else {
// Do your stuffs
}
}

I know my answer is not using -(void)willMoveToParentViewController:(UIViewController *)parent, but the easiest would be to just display no back button.
This can be done via:
self.navigationItem.hidesBackButton = YES;
You can also disable the swipe gesture as described here:
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
But if you really want to hack in there then I would try a custom back button and handle the action in the controller.

I just made a custom button in IB and attached to it. Issue solved.

Related

viewDidDisappear called because of termination? [duplicate]

When a view loads, i want to see if it's because the user pressed the back button. How can i check this?
The best solution I've found to detect a UINavigationController's back button press (pre-iOS 5.0) is by verifying that the current view controller is not present in the in the navigation controller's view controller stack.
It is possibly safer to check this condition in - (void)viewDidDisappear:(BOOL)animated as logically, by the time that method is called it would be extremely likely that the view controller was removed from the stack.
Pre-iOS 5.0:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (![[self.navigationController viewControllers] containsObject:self]) {
// We were removed from the navigation controller's view controller stack
// thus, we can infer that the back button was pressed
}
}
iOS 5.0+ you can use -didMoveToParentViewController:
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// parent is nil if this view controller was removed
}
in your viewWillDisappear method check
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]) {
//specific stuff for being popped off stack
}
}
This is only for post iOS 5
UINavigationController has a delegate property that issues delegate callbacks. Please see the iOS reference here.
The delegate doesn't have a "back button pressed" callback, but instead it tells you when something is going to appear on the navigation stack. When you press back, you are "popping" the top view controller off the stack, so it will tell you that the view is about to appear. I think this is the callback you'd be looking for.
You could have some simple logic to check if it's the view controller that's "interested", and then you could send a notification, et al.
For the sake of completeness, mix of two most upvoted answers (1, 2) in Swift:
override func willMoveToParentViewController(parent: UIViewController?) {
super.willMoveToParentViewController(parent)
if parent == nil {
// view controller is popping
}
}
This is a slightly different scenario, but I thought the solution might help others out.
In my situation, I had a UINavigationController within a UIPopoverController. I needed to detect whether the user clicked the back button, or clicked outside of the popover. To do this I checked the visibleViewController property in viewWillDisappear. If the view controller is still the visibleViewController when closing, then the popover is being closed by another means. If the view controller is not the visibleViewController when closing, then the back button was pressed.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.navigationController.visibleViewController != self) {
<Do something since we're closing using something else>
} else {
<Do something since we're closing because of the back button>
}
}
I tried using zach's solution, but isMovingFromParentViewController returns true for both cases.
I verified this works in iOS 5+
I hope this helps.
Create a custom back bar button and set the target,
Step 1: Add these methods to your class
- (void)backButtonClicked :(id)sender{
[self.navigationController popViewControllerAnimated:YES];
}
- (void)addBackBarButton{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 55, 35);
[button setTitle:#"back" forState:UIControlStateNormal];
[button addTarget:self action:#selector(backButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *customBarItem = [[UIBarButtonItem alloc] initWithCustomView:button];
self.navigationItem.leftBarButtonItem = customBarItem;
}
Step 2: Call [self addBackBarButton]; in viewDiDLoad method
You will get the action in backButtonClicked method. You can play around with it the way you want.
Cheers!
The only way to do this so you know for sure that it was the back button is to create a custom button. If you don't know how to do that, check out this tutorial. It won't look exactly like the normal back button, but close. If you need more help, post a comment

Triggering an event when user clicks "< Back" button

I have fairly simple UINavigationControllers with two screens: screen1 & screen2.
When the user is on screen2 and clicks < Back I want to trigger an event before screen1 is shown -- basically I want to fetch data from the server for screen1.
Is there a way to do this?
In your viewDidLoad do this:
UIBarButtonItem *backBarButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"icon_back.png"] style:UIBarButtonItemStylePlain target:self action:#selector(goBack:)];
self.navigationItem.leftBarButtonItem = backBarButton;
Implement the method goBack:
- (void)goBack:(id)sender {
//Do something
[self.navigationController popViewControllerAnimated:YES];
}
Hope this helps.. :)
If you're using storyboard, you can handle your back action in.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
Only by doing it in viewWillDisappear. Unfortunately it will be triggered also when you present a new screen over the current one, not just when you tap the back button. You could however add a custom back button, and place your code in it's action method. After the operation is finished, call [self.navigationController popViewControllerAnimated:YES]
-(void)viewWillDisappear:(BOOL)animated{
if (![self.navigationController.viewControllers containsObject:self]) {
NSLog(#"Back Event");
}
}
You can do in two way.
Create custom button and set event.
Check on viewWillDisappear when users pop the screen

Detecting if Back Button Pressed Issue - GLKViewController

I am trying to detect when the user pressed the back button in a GLKViewController. The view controller is presented via a storyboard from the root view controller (which is a menu).
The problem is that I cannot detect when the back button is pressed (so I can free up memory).
I have tried :
if (self.isMovingFromParentViewController) {
// Do your stuff here
NSLog(#"Dismissed");
}
And
if (self.isBeingDismissed) {
// Do your stuff here
NSLog(#"Dismissed");
}
Neither of which fires. Can anyone suggest why ?
you could make your own action to BackButton:
[self.navigationItem.backBarButtonItem setAction:#selector(action:)];
-(void) action:(id)sender {
//do something here
[self.navigationController popViewControllerAnimated:YES];
}

iOS: Pushing a view controller with animation only works once

I am creating an iPhone client for one of my apps that has an API. I am using the GTMOAuth2 library for authentication. The library takes care of opening a web view for me with the correct url. However I have to push the view controller myself. Let me show you some code to make things more clear:
- (void)signInWithCatapult
{
[self signOut];
GTMOAuth2ViewControllerTouch *viewController;
viewController = [[GTMOAuth2ViewControllerTouch alloc] initWithAuthentication:[_account catapultAuthenticaiton]
authorizationURL:[NSURL URLWithString:kCatapultAuthURL]
keychainItemName:kCatapultKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[self.navigationController pushViewController:viewController animated:YES];
}
I have a "plus"/"add" button that I add to the view dynamically and that points to that method:
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(signInWithCatapult)];
When I press the "add" button, what is supposed to happen is to open the web view with an animation, and then add an account to the accounts instance variable which populates the table view. This works fine if I add one account, but as soon as I try to add a second account, the screen goes black and two errors appear in the console:
nested pop animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
The only way that I found to avoid this problem was to disable animations when pushing the view controller.
What am I doing wrong please?
Typical situations
You push or pop controllers inside viewWillAppear: or similar methods.
You override viewWillAppear: (or similar methods) but you are not calling [super viewWillAppear:].
You are starting two animations at the same time, e.g. running an animated pop and then immediately running an animated push. The animations then collide. In this case, using [UINavigationController setViewControllers:animated:] must be used.
Have you tried the following for dismissing once you're in?
[self dismissViewControllerAnimated:YES completion:nil];
I got the nested pop animation can result in corrupted navigation bar message when I was trying to pop a view controller before it had appeared. Override viewDidAppear to set a flag in your UIViewController subclass indicating that the view has appeared (remember to call [super viewDidAppear] as well). Test that flag before you pop the controller. If the view hasn't appeared yet, you may want to set another flag indicating that you need to immediately pop the view controller, from within viewDidAppear, as soon as it has appeared. Like so:
#interface MyViewController : UIViewController {
bool didAppear, needToPop;
}
...and in the #implementation...
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
didAppear = YES;
if (needToPop)
[self.navigationController popViewControllerAnimated:YES];
}
- (void)myCrucialBackgroundTask {
// this task was presumably initiated when view was created or loaded....
...
if (myTaskFailed) { // o noes!
if (didAppear)
[self.navigationController popViewControllerAnimated:YES];
else
needToPop = YES;
}
}
The duplicated popViewControllerAnimated call is a bit ugly, but the only way I could get this to work in my currently-tired state.

How can I determine whether a back button is displayed?

In an iOS app, how can I determine whether a back button is displayed? Ideally, I'd like to know this in my controller's loadView method.
Here's what I've tried in loadView, viewDidLoad, and viewWillAppear:
if (self.navigationItem.backBarButtonItem)
and this:
if (self.navigationItem.leftBarButtonItem)
Neither of those work - they're always nil (the expression evaluates to false), even when there is a back button on the screen. What I'd ultimately like to do is set a Cancel button as the self.navigationItem.leftBarButtonItem, but only if there's no back button. If there is a back button, we don't need the Cancel button. As it is, setting the leftBarButtonItem is overriding the back button, so we're seeing a Cancel button all the time - even when there should be a back button.
You're asking the wrong object for its backBarButtonItem. This property controls how an object is represented when it is the "back" item in the navigation stack.
Therefore, you need to be asking the view controller at the level below where you are in the navigation stack what its backBarButtonItem is:
int n = [self.navigationController.viewControllers count] - 2;
if (n >= 0)
if ([(UIViewController*)[self.navigationController.viewControllers objectAtIndex:n]navigationItem].backBarButtonItem == nil)
// Do your thing....
You may need to check if the navigation controller has added your viewController to the stack at the time you are executing this code, the top view controller may still be the previous one. I've checked in viewWillAppear and the stack does contain the new top controller at this point.
NSLog(#"%#",self.navigationController.navigationBar.backItem);
if (self.navigationController.navigationBar.backItem == NULL) {
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(addLaunch)];
self.navigationItem.leftBarButtonItem = cancelButton;
[cancelButton release];
}
this code works, its in the viewDidAppear, if your controller is null, theres no button, so i add the cancel button, its not a back item though, just a regular:] hope this helps
loadView is too early. The earliest you could check this is in viewDidLoad. You may need to wait for viewWillAppear.
Search for the following
self.navigationController.navigationBar.backItem
instead of backBarButtonItem.
If backItem exists then you are on navigation stack further than rootViewController.
Just use this code:
if (![[[self.parentViewController childViewControllers] firstObject] isKindOfClass:[self class]]) {
}

Resources