I want to mark that my UINavigationController is animating (push/pop) or not.
I have a BOOL variable (_isAnimating), and the the code below seem work:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
_isAnimating = YES;
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
_isAnimating = NO;
}
But it work incorrectly in iOS7 with swipe gesture. Assume my navigation is: root-> view A -> view B . I'm currently on B.
In begin of swipe (go back from B to A), the funcion "navigationController:willShowViewController:animated:(BOOL)animated" is called, then _isAnimating = YES.
The normal case is the swipe is finished (go back to A), the function "navigationController:didShowViewController:animated:(BOOL)animated" is called, then _isAnimating = NO. This case is OK, but:
If the user may just swipe a half (half transition to A), then don't want to swipe to the previous view (view A), he go to the current view again (stay B again). Then the function "navigationController:didShowViewController:animated:(BOOL)animated" is not called, my variable has incorrect value (_isAnimating=YES).
I have no chance to update my variable in this abnormal case. Is there any way to update the state of navigation? Thank you!
The clue to solve you problem can be found in the interactivePopGestureRecognizer property of UINavigationController. This is the recognizer which responds for popping controllers with a swipe gesture. You can notice that the state of the recognizer is changed to UIGestureRecognizerStateEnded when the user rises finger up. So, additionally to Navigation Controller delegate you should add target to the Pop Recognizer:
UIGestureRecognizer *popRecognizer = self.navigationController.interactivePopGestureRecognizer;
[popRecognizer addTarget:self
action:#selector(navigationControllerPopGestureRecognizerAction:)];
This action will be called each time the Pop Recognizer changed, including the end of a gesture.
- (void)navigationControllerPopGestureRecognizerAction:(UIGestureRecognizer *)sender
{
switch (sender.state)
{
case UIGestureRecognizerStateEnded:
// Next cases are added for relaibility
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
_isAnimating = NO;
break;
default:
break;
}
}
P.S. Do not forget that the interactivePopGestureRecognizer property is available since iOS 7!
Related
I have the following VC hierarchy: ParentViewController -> Navigation Controller(child VC) -> ViewController(with UITableView).
I am meeting the following issue:
In UITableViewDelegate.didSelectRowAtIndexPath, the app is pushing a new ViewController on the stack. The problem is that at this stage, the table view is automatically reloaded, without any explicit call to reloadData. This fact creates several issues, for example on return to the screen(with Back button) the table view is scrolled at the beginning instead of being focused on the selected row.
Could you please help me find why is it doing like so, is it a bug, or how to fix the issue?
UPDATE: I have just tested the same case on iOS 7.1 and there is no issue like that, I mean pushing a viewController, than poping back to the viewController containing the UITableView does not loose focus of the selected row.
hope this could help you.
My solution is to use navigationviewcontroller delegate, save selection when will push another one, and set back when pop back.
My sample code(I have set table view controller as delegate)
static NSIndexPath* path = nil;
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (viewController == self)
{
[self.tableView reloadData];
[self.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionMiddle];
}
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
path = self.tableView.indexPathForSelectedRow;
}
I have a subclass of UINavigationController that has maximum of 4 ViewControllers in the stack. Lets call them firstVC ... fourthVC. My NavController can perform custom transitions between VCs and ios7/8 back gesture is supposed to be disabled and enabled depending on which VC is currently at the top of the stack. I've set my root VC (firstVC) as a NavController's delegate and trying to enable/disable back gesture in the delegate's method
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController respondsToSelector:#selector(needsBackGestureEnabled)]) {
[self.navigationController.interactivePopGestureRecognizer setEnabled:YES];
NSLog(#"Back gesture enabled");
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
} else {
if ([navigationController.interactivePopGestureRecognizer isEnabled]) {
[self.navigationController.interactivePopGestureRecognizer setEnabled:NO];
NSLog(#"Back gesture disabled");
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
}
}
It works like a charm except one glitch. I feel like a short scheme might explain situation better:
FirstVC -[CustomTran]-> SecondVC -[push]-> ThirdVC -[push]-> FourthVC
FourthVC is the only one that have -needsBackGestureEnabled selector, but after transition from second to third back gesture gets enabled by itself. Even though the back button is susbtituted with the CustomBarButtonItem. I feel like performing default -pushViewController animation makes back gesture enabled somehow. I tried to ecplicitly disable it in my NavController subclass in -pushViewController but it didn't change a thing. Any idea why this is happening and how to fix this?
Scenario
I have an app with a navigation controller. When the navigation controller pushes another controller onto the stack, in the upper left corner of the screen it shows the back button "<(title of the last view controller)".
What I need
I need something like (pseudo code)...
-(void)detectedBackButtonWasPushed {
NSLog(#"Back Button Pressed");
//Do what I need done
}
Question
Because this button is created by the navigation controller and I did not create this button in storyboards, how do I get the back button 'hooked up' to a method like this?
examples of what Ive tried for Oleg
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"notification"];
if (viewController == vc) {
NSLog(#"BACK BUTTON PRESSED");
}
}
Is this how I'm supposed to do it? Cause this doesn't work.
Use viewWillDisappear to detect this.
-(void) viewWillDisappear:(BOOL)animated
{
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
{
[self backButtonPressed];
[self.navigationController popViewControllerAnimated:NO];
}
[super viewWillDisappear:animated];
}
-(void)backButtonPressed
{
NSLog(#"YEA");
}
Previously, I have solved this by setting the navigationBar leftItem to be a back button with a custom selector that dismisses the view along with whatever else it needed to do.
I might also suggest looking at the back button item and adding a target:self that is called on touch.
I have a UINavigationBar that intercepts the back button tap that alerts the user if there are unsave changes. This is based on the solution presented in UINavigationController and UINavigationBarDelegate.ShouldPopItem() with MonoTouch using the UINavigationBarDelegate protocol and implementing - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
Now, iOS7 has introduced the swipe-to-go-back gesture, and I'd like to intercept that as well, but can't get it to work with the solutions I've found so far, namely using [self.interactivePopGestureRecognizer addTarget:self action:#selector(handlePopGesture:)]; and
- (void)handlePopGesture:(UIGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateEnded) {
[self popViewControllerAnimated:NO];
}
}
While this does pop the views, it leaves the navigation bar buttons in place, so I'm ending up with a back button that leads nowhere, as well as all other navigation button I've added to the nav bar. Any tips?
To intercept the back swipe gesture you can set self as the delegate of the gesture (<UIGestureRecognizerDelegate>) and then return YES or NO from gestureRecognizerShouldBegin based on unsaved changes:
// in viewDidLoad
self.navigationController.interactivePopGestureRecognizer.delegate = self;
// ...
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
if (self.dirty) {
// ... alert
return NO;
} else
return YES;
} else
return YES;
}
In the alert you can ask to the user if she want to go back anyway and, in that case, pop the controller in alertView clickedButtonAtIndex:
Hope this is of some help.
Hi there, Now I'm trying to create a Pop-OverView using an Xcode
storyboard. Firstly, I have
rootViewController, UIViewController, and UITableViewController
I want the UIView to act as a page flip and the UITableView will show popOver under the navigationBar item controller.
For the UITableView, I want to make a Pop-Over under NavigationBar controller. The problem is, when I touch the Navigation item to show the UITableViewController, it shows correctly, but when I try to close the Pop-Over View, it won't close. And then, the navigation item doesn't work well. It shows multiple instances of popOverView when I touch it multiple times.
This doesn't seem to make sense to me. Can anyone help me out or tell me where to find documentation / tutorials on this?
UPDATE:
For the UIPopOverController, it seems to work well now, but it is still bugging me when I touch a Navigation Item multiple times. It will show multiple instances of PopOver. How can I handle it, so it will show only one instance?
I had the same problem and mostly found the solution here. Basically you change the action of the button each time it's pressed to either display or dismiss the popover. Here's the code I ended up with:
#interface FilterTableViewController : UITableViewController {
UIPopoverController *editPopover;
id saveEditSender;
id saveEditTarget;
SEL saveEditAction;
}
-(void)prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender{
if([[segue identifier] isEqualToString:#"EditFilterSegue"]){
// Save the edit button's info so we can restore it
saveEditAction = [sender action];
saveEditTarget = [sender target];
saveEditSender = sender;
// Change the edit button's target to us, and its action to dismiss the popover
[sender setAction:#selector(dismissPopover:)];
[sender setTarget:self];
// Save the popover controller and set ourselves as the its delegate so we can
// restore the button action when this popover is dismissed (this happens when the popover
// is dismissed by tapping outside the view, not by tapping the edit button again)
editPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
editPopover.delegate = (id <UIPopoverControllerDelegate>)self;
}
}
-(void)dismissPopover:(id)sender
{
// Restore the buttons actions before we dismiss the popover
[saveEditSender setAction:saveEditAction];
[saveEditSender setTarget:saveEditTarget];
[editPopover dismissPopoverAnimated:YES];
}
-(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
// A tap occurred outside of the popover.
// Restore the button actions before its dismissed.
[saveEditSender setAction:saveEditAction];
[saveEditSender setTarget:saveEditTarget];
return YES;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Before we navigate away from this view (the back button was pressed)
// remove the edit popover (if it exists).
[self dismissPopover:saveEditSender];
}