实现一个简单的面包屑导航

往期文章

寻找IOS相册中相似图片
Multipeer Connectivity 近场多点通信
实现一个简单沙盒文件浏览器

实现效果

主要功能如下

  • push视图,面包屑导航条自动增加对应的视图的Item

  • 点击对应的面包屑导航条Item,跳转到Item所对应的文字

  • 面包屑导航条item的内容宽度大于当前屏幕宽度的时候,面包屑导航条开启滑动模式并自动移动到最后一项,保证当前视图对应的面包屑导航条的最后一个Item

  • 面包屑导航条item的内容宽度小于或者等于当前屏幕宽度的时候,面包屑导航条关闭滑动模式

演示效果如下

Simulator Screen Recording - iPhone 8 - 2021-11-06 at 15.08.45.gif

实现过程

实现主要分为两个板块来讲,一个是面包屑导航控制器,一个是面包屑导航条。

面包屑导航控制器

当前版本没有针对UINavigationController侧滑返回功能做出处理,所以暂时关闭了UINavigationController的侧滑返回功能

面包屑导航条的添加

为了方便管理控制器的出栈和入栈,需要继承系统给我们提供的UINavigationController

@interface AMBreadCrumbNavController : UINavigationController

@end
复制代码

如下图所示,如果使用系统提供的UINavigationController,会有自带的NavigationBar。因此我们需要将自带的NavigationBa给隐藏掉,并添加上我们自己的面包屑导航条。

Simulator Screen Shot - iPhone 8 - 2021-11-06 at 15.29.32.png

在添加到导航条之后,我们需要给告诉自定义的面包屑导航条,当前导航控制器中的控制器栈,并将这个传入的根视图控制器,放入的控制器栈当中。然后交由自定义面包屑导航条管理。

并且将面包屑导航条的协议方法交给当前导航控制器AMBreadCrumbNavController实现。

实现代码如下:

- (instancetype)initWithRootViewController:(UIViewController *)rootViewController {

    //super中已经初始化好导航条了

    if (self = [super initWithRootViewController:rootViewController]) {
        //将导航栏设置为不透明
        self.navigationBar.translucent = NO;
        //设置根视图
        self.rootViewController = rootViewController;
        //分类添加一个面包屑导航条,用来覆盖掉系统自带的导航条
        [self breadCrumbNav];
        //初始化根视图控制器导航栏
        self.breadCrumbBar.controllers = @[self.rootViewController];
        //设置面包屑导航代理
        self.breadCrumbBar.delegate = self;
        //隐藏自带导航条
        [self setNavigationBarHidden:YES];
    }

    return self;
}
复制代码

这里将面包屑导航条的添加方法,改为分类UIViewController+AMBreadCrumbNav实现,目的是为了将来给其他类型的控制器添加面包屑导航,而不单单为了UINavigationController设计。

@interface UIViewController (AMBreadCrumbNav)

/// 面包屑导航栏整个导航栏视图
@property(strong,nonatomic) UIView* breadCrumbView;

/// 面包屑功能条
@property(strong,nonatomic) AMBreadCrumbNavBar* breadCrumbBar;

/// 右侧拓展视图
@property(strong,nonatomic) UIView* rightView;

/// 给右侧拓展视图添加自定义控件
/// **@param** item 右侧控件
- (void) addRightItem:(UIView*) item;

///// 右侧拓展按钮
//@property(strong,nonatomic) UIButton* selectButton;

- (void) breadCrumbNav;
@end
复制代码

控制器的出栈和入栈

接下来我们要来实现这个导航控制器的入栈控制器和出栈控制器的方法。

控制器入栈

入栈方法很简单,我们只需要重写UINavigationController的pushViewController,并且将最新的控制器数组传递给面包屑导航条,使其更新导航条内容。

实现代码如下


- (void) pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    //关闭侧滑返回
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
        self.interactivePopGestureRecognizer.enabled = NO;
    //先入栈
    [super pushViewController:viewController animated:animated];
    //更新面包屑导航条
    self.breadCrumbBar.controllers = self.viewControllers;
}
复制代码

控制器出栈

因为关闭了导航控制器的侧滑返回功能,而且导航条上也没有对应的出栈按钮。所以我想要实现控制器的出栈,只有点面包屑导航条上的控制器对应的按钮,从而切换到指定的控制器。

所以这里我们需要实现面包屑导航条的委托方法。从而获取到当前在面包屑导航条上所点击的控制器,也就是我们想要切换到控制器。

当获取到了想要切换到的控制器之后,我们就需要将这个控制器之后的控制器全部出栈,只保留当前想要切换的控制器以及它之前的控制器

比如 A - B - C - D这个控制器栈,我们当前在D控制器页面,当我们点击B之后,clickViewController代理方法给我们回传需要切换到的控制器B,这时候我们就需要将C 和 D两个控制器出栈,只保留A和B两个控制器在栈内。

-(void)clickViewController:(UIViewController *)viewController{

    if ([self.viewControllers containsObject:viewController]) {
        if ([self.visibleViewController isEqual:viewController]) {
            return;
        }
        [self popToViewController:viewController animated:YES];
       //判断当前ViewControllers栈底元素是否是根视图,因为如果是跨页面跳转,比如A-B-C-D,D-C跳转 self.viewControllers栈底元素是根视图A,但是D-B跳转,self.viewControllers栈底元素却不是根视图A
        if (![self.viewControllers.firstObject isEqual:self.rootViewController]) {
            //删除不是根视图的栈底元素
            [self.viewControllers.firstObject removeFromParentViewController];
        }
        //更新面包屑导航条
        self.breadCrumbBar.controllers = self.viewControllers;
    }
}
复制代码

面包屑导航条

面包屑导航条内部是由一个UICollectionView来实现的,每一个控制器对应CollectionView中的一个item。

item分为文字区和拓展区

  • 文字区主要显示当前控制器要在面包屑导航条中的显示的文字。
  • 拓展区,主要用于当item的数量大于2的时候,给非最后一个item添加面包屑导航条的分割符号。这里我们用的是一个简单 > 符号来替代,后期可以根据自身需求替换成动画或者图片。

image.png

当然每个item的宽度是由所要显示的文字加上拓展区的宽度来计算的。也就是说当不需要显示拓展区的时候(item只有一个或者最后一个item)时,item的宽度就等于文字区的宽度。

item显示数据获取

这里我们通过获取面包屑导航控制器(AMBreadCrumbNavController)中的控制器栈(controllers),将其传递给AMBreadCrumbNavBar

@interface AMBreadCrumbNavBar : UIView
/// 保存系统导航控制器入栈的控制器
@property(strong,nonatomic) NSArray<UIViewController*>* controllers;
@property(weak,nonatomic) id<AMBreadCrumbBarDelegate> delegate;

@end
复制代码

通过controllers我们可以获取到,整个控制器栈中的控制器,然后根据控制器UIViewController中的navigationItem属性,拿到需要显示的title。然后交由AMBreadCrumbNavCell来显示。


/// AMBreadCrumbNavCell
- (void)setController:(UIViewController *)controller{
    _controller = controller;
    NSLog(@"标题是 %@",controller.navigationItem.title);
    self.titleLabel.text = controller.navigationItem.title;

}
复制代码

自动滑动到末尾

在当前屏幕宽度无法显示CollectionView全部内容时设置面包屑导航条自动滑动到末尾

实现原理很简单,就是利用CollectionView的scrollToItemAtIndexPath方法

该方法在指定的item可见的时候不会触发,在指定item不可见的时候,自动滚动到指定的item。

所以我们只需要在面包屑导航条(AMBreadCrumbNavBar)加载数据的时候,获取最一个控制器,也就是最后一个需要显得item,并让CollectionView滚动到他。就可以实现,当末尾item不可见的时候,自动滚到到末尾item。

实现代码如下

- (void)setControllers:(NSArray<UIViewController *> *)controllers{
    _controllers = controllers;
    [self.collectionView reloadData];
    //如果导航条无法展示所有cell,则将collectionView自动滑动到末尾。
    [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:controllers.count-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}

复制代码

注意scrollToItemAtIndexPath指定滚动的时候,一定要先让CollectionView刷新,在指定滚动。

项目地址

如果有问题欢迎给我issues,我会尽力解决

后续会不断完善该项目。

AMBreadcrumbs

猜你喜欢

转载自juejin.im/post/7027381839526379527