I haven’t written UI for a long time, and I’m unfamiliar with a lot of things in UI.

1. Display and hide the navigation bar

Navigation bar display and hide, divided into two cases:

  • 1. Push the page that does not display the navigation bar to the page that displays the navigation bar.
  • 2. Push from the page that displays the navigation bar to the page that does not.

Note: 1. If the navigation bar is not displayed, the system’s slide back function is invalid. 2. Although the sideslip return function is invalid, but the navigation bar. InteractivePopGestureRecognizer. There is a delegate.

In view of the above two cases, the whole Push process is assumed to jump from page A to page B

1.1 Push the page from not showing the navigation bar to the page showing the navigation bar.

As for the display of the navigation bar, whether it is smooth or not is controlled by the following two methods.

/ / do not display animations, navigation display is abrupt [self. NavigationControllersetNavigationBarHidden:YES]; / / show animation in sideslip, navigation display is smooth [self. NavigationControllersetNavigationBarHidden:YES animated:YES];
Copy the code

So, the approach is: A page:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    [self.navigationController setNavigationBarHidden:YES animated:YES];
}
Copy the code

B page:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    [self.navigationController setNavigationBarHidden:NO animated:YES];
}
Copy the code

1.2 Switch from the page with the navigation Bar displayed to the page without the navigation bar

In this case, the approach is as follows:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:NO animated:YES];
}
Copy the code

B page:

- (void)viewWillAppear:(BOOL)animated {[super viewWillAppear:animated]; self.interactivePopDelegate = self.navigationController.interactivePopGestureRecognizer.delegate; self.navigationController.interactivePopGestureRecognizer.delegate = self; [self.navigationControllersetNavigationBarHidden:YES animated:YES]; } / / in the page disappears, reduction of sideslip gestures proxy object - (void) viewDidDisappear: (BOOL) animated {[super viewDidDisappear: animated]; self.navigationController.interactivePopGestureRecognizer.delegate = self.interactivePopDelegate; self.interactivePopDelegate = nil; } / / implementation gestures agent, in order to prevent affect other gestures, can determine the gesture type - (BOOL) gestureRecognizerShouldBegin: (gestureRecognizer UIGestureRecognizer *) {if ([gestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]]) {
        returnYES; }... Processing of other gesturesreturn NO;
}

Copy the code

2. Rewrite the navigation back button

Sometimes, we may need to unify the project’s return button style, such as all arrows + return or all arrows. There are two options:

  • 1. Create a BaseViewController, and unified set navigationItem leftBarButtonItem.
  • 2. Override the navigation controller’s Push method and set it before PushnavigationItem.backBarButtonItem.

Note: If the leftBarButtonItem of the navigation bar is overwritten, then the sideslip return function is disabled, and the sideslip return function needs to be handled by itself.

The first option is relatively simple and will not be repeated. The second option is as follows:

Customize the navigation controller, then override the following method:

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"Return" style:UIBarButtonItemStyleDone target:nil action:nil];
    viewController.navigationItem.backBarButtonItem = backItem;
    
    [super pushViewController:viewController animated:animated];
}
Copy the code

If you don’t need to return these two words, just write them like this.

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:nil style:UIBarButtonItemStyleDone target:nil action:nil];
    viewController.navigationItem.backBarButtonItem = backItem;
    
    [super pushViewController:viewController animated:animated];
}
Copy the code

3. Listen for the click event of the back button

In some scenarios, we need to listen for events that return buttons. For example, when a user enters something on a page, the user clicks back, and when he wants to return to the previous page, the user is reminded whether to cache the entered content.

If we override the back button in the navigation bar, it’s Easy to handle this situation without going into detail. However, if we hadn’t overridden the system’s back button, it would be a bit more cumbersome, but manageable, to handle.

1. Create a UIViewController class with the following header (.h) :

@protocol BackItemProtocol <NSObject>

- (BOOL)navigationShouldPopWhenBackButtonClick;

@end

@interface UIViewController (BackItem)<BackItemProtocol>

@end

@interface UINavigationController (BackItem)

@end
Copy the code

Contains a protocol, UIViewController category, UINavigationController category.

Then, the implementation file (.m) looks like this:

#import "UIViewController+BackItem.h"

@implementation UIViewController (BackItem)

- (BOOL)navigationShouldPopWhenBackButtonClick
{
    returnYES; } @end@implementation UINavigationController (BackItem)} @implementation UINavigationController (BackItem) - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {if([self.viewControllers count] < [navigationBar.items count]) {
        return YES;
    }
    
    BOOL shouldPop = YES;
    UIViewController *vc = [self topViewController];
    if([vc respondsToSelector:@selector(navigationShouldPopWhenBackButtonClick)]) {
        shouldPop = [vc navigationShouldPopWhenBackButtonClick];
    }
    
    if (shouldPop) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self popViewControllerAnimated:YES];
        });
    } else {
        for(UIView *subview in [navigationBar subviews]) {
            if(subview.alpha < 1) { [UIView animateWithDuration:.25 animations:^{ subview.alpha = 1; }]; }}}return NO;
}

@end
Copy the code

By default, you don’t need to handle the event of the return button and use the system’s POP method directly.

However, if we need a pop-up prompt when the user clicks the back button, we need to import this category. Then, rewrite a method:

- (BOOL) navigationShouldPopWhenBackButtonClick {BOOL isFlag = input box is not null, and so onif (isFlag) {
        UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:nil message:@"Save the changes?" preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"Style: UIAlertActionStyleCancel handler: ^ (UIAlertAction * _Nonnull action) {/ / delayed execution here because UIAlertController blocking the UI, Dispatch_after (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.navigationController popViewControllerAnimated:YES]; }); }]; UIAlertAction *saveAction = [UIAlertAction actionWithTitle:@"Save"Style: UIAlertActionStyleDefault handler: ^ (UIAlertAction * _Nonnull action) {/ / delayed execution here because UIAlertController blocking the UI, Dispatch_after (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self rightClick]; }); }]; [alertVC addAction:cancelAction]; [alertVC addAction:saveAction]; [self presentViewController:alertVC animated:YES completion:nil];return NO;
    }
    
    return YES;
}
Copy the code

4. Navigation Controller page hopping mode

There are four modes of page jump in Android: Standard, singleTop, singleTask and singleInstance.

SingleTask, for example, is very useful in the scenario of jumping to a chat room when making an IM class App. It can ensure that there is only one chat room in the controller stack and avoid returning to too deep a hierarchy.

To emulate this effect on iOS, you can use the navigation controller API:

- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:(BOOL)animated
Copy the code

First, create a category for the UINavigationController. Such as:

UINavigationController+HLPushAndPop.h
UINavigationController+HLPushAndPop.m
Copy the code

Then, add a few methods:

Take two examples

- (void)hl_pushSingleViewController:(UIViewController *)viewController
                           animated:(BOOL)animated;

- (void)hl_pushSingleViewController:(UIViewController *)viewController
                         parentClass:(Class)parentClass
                            animated:(BOOL)animated;
Copy the code

Then, the implementation method:

Implementation steps:

    1. Create a new array copy navigation controller from the original stack of controllers.
    1. Determines whether a controller of this type exists in the original stack array, and if so, records its index.
    1. Removes the index and all controllers above the copied array.
    1. Add the controller to be pushed to the copied array.
    1. Set the new controller array to the stack array for the navigation controller, and determine whether to animate based on the parameters.

I’m doing a little bit of divergence here, because some classes might have a lot of subclasses, so I want to make sure that there’s only one instance of both the parent class and the subclass, so I changed the method.

- (void)hl_pushSingleViewController:(UIViewController *)viewController
                           animated:(BOOL)animated
{
    [self hl_pushSingleViewController:viewController parentClass:viewController.class animated:animated];
}

- (void)hl_pushSingleViewController:(UIViewController *)viewController
                        parentClass:(Class)parentClass
                           animated:(BOOL)animated
{
    if(! viewController) {return; } // If the interface to be pushed is not an instance of parentClass and its subclasses, proceed as per method 1if(! [viewController isKindOfClass:parentClass]) { [self hl_pushSingleViewController:viewController animated:animated];return; } / / whether the navigation controller stack parentClass and its subclass instance NSArray * childViewControllers = self childViewControllers; NSMutableArray *newChildVCs = [[NSMutableArray alloc] initWithArray:childViewControllers]; BOOL isExit = NO; NSInteger index = 0;for (int i = 0; i < childViewControllers.count; i++) {
        UIViewController *vc = childViewControllers[i];
        if ([vc isKindOfClass:parentClass]) {
            isExit = YES;
            index = i;
            break; }} // If it doesn't exist, push itif(! isExit) { [self pushViewController:viewController animated:animated];return; } // If so, push the instance and all the interfaces above it off the stack, and then push the interface to the top of the stack.for (NSInteger i = childViewControllers.count - 1; i >= index; i--) {
        [newChildVCs removeObjectAtIndex:i];
    }
    
    [newChildVCs addObject:viewController];
    viewController.hidesBottomBarWhenPushed = (newChildVCs.count > 1);
    [self setViewControllers:newChildVCs animated:animated];
}

Copy the code

Of course, in addition to these scenarios, we can extend some other scenarios, such as the controller we want to push behind or in front of the controller on a stack, so that when we click back or swipe, we are directly back to the specified page.

Or we know the type of page to be returned and pop back to the specified page.

The rest of the extended methods are in the Demo, if you’re interested.

The address is HLProject