1行代码为每个Controller自定义“TabBar”-b

这篇文章大致会带你实现以下的功能,废话少说,先看东西:


JPNavigationController.gif

Q&A:Demo里都有那些东西?

01、关于自定义导航栏
  • 01、第一个控制器的导航条是透明的,第二个控制器的导航条是白色的,第三个控制器的导航条是橙色的。
    所以,为每个控制器定制自己的导航条。
  • 02、支持全屏右滑,这简直是必须的。关于全屏右滑,最详细,也最早探究这个问题的,我了解到的是 J_雨 ,他应该是全屏右滑的鼻祖。
  • 03、最重要的一点,要求全屏右滑返回的时候,导航条跟随自己的控制器流畅的滑动。
  • 04、以下属于更新的内容,为全屏右滑Pop手势添加滑动起始点控制API。也即是,你可以精确控制从离屏幕左侧起多长距离内右滑,Pop手势是有效的。
  • 05、某些同学和我说,他们在某些界面需要临时关闭Pop手势,让我处理一下。现在已经处理好了,你可以临时关掉Pop手势,等你需要的时候再次打开。
02、此次新增加自定义TabBar

现有问题

  • 某些同学做购物软件,可能需要在某个控制器屏幕最下方一直悬浮一个“立即购买”、“马上咨询”之类的控件。有经验的同学遇到此类功能时可能会采用新建一个Window,然后再把自己需要的控件添加到这个Window上,然后设置这个Window的hidden = NO,就可以实现了。确实是这样,其实除了新建一个Window,你还可以将自己的控件添加到当前KeyWindow上,同样可以实现这个功能。
  • 这个功能实现了,但是有一点很恶心的是,苹果自从iOS7开始,有了右滑手势这个东西。这个时候,相比于安卓前进后退按钮是单一的在窗口显示一个控制器,右滑手势可以让窗口同时显示两个控制器的View。
  • 如果你按照上面添加Window或是添加在KeyWindow上的方式。第一、你就不能让你定义的这个控件随着用户右滑而右滑。第二,你只能在这两个方法里控制你的控件的显示和隐藏。但是试过以后你会发现,当用户右滑的时候,你的控件一直在跳跃闪烁。但是我们是有追求的程序狗,我们显然会对这种方案SAY:FUCK AWAY!THIS IS SO UGLY MAN. AND IT SMELLS LIKE SHIT.
    -(void)viewWillAppear:(BOOL)animated
    -(void)viewWillDisappear:(BOOL)animated

解决方案

  • 此次我结合之前的自定义导航栏,给出了一个比较“优雅”的解决方案:
  • 假如A是根控制器,B是下一个要Push过去的控制器,你只需要在A里Push方法后写如下一行。你就可以拥有一个和导航栏一样跟随用户右滑而流畅滑动的“联动”TabBar了,并且你不用关心这个TabBar 的显示和隐藏。
    [self.navigationController pushViewController:YourOwnVC animated:YES];
    YourOwnVC.navigationController.jp_linkViewHeight = YourHopeHeight;

01、之前文章讲了啥?

看到我这篇文章的同学99%都是没有看过我之前那篇文章的,所以我有必要再重新描述下之前文章的内容。但是如果你要详细了解实现思路还是需要回头看我那篇文章,因为这篇文章我是讲TabBar的,所以NavgationController不会讲的很细。
之前的文章大致说清楚了,怎么样去适应需要为每个界面都定制导航条的需求。
实现思路如下:

  • 当我们调用这个initWithRootViewController构造方法的时候,先用一个JPNavigationController作为根控制器,负责所有的Push和Pop操作。
    [[JPNavigationController alloc]initWithRootViewController:vc];
  • 然后当用户调用Push方法的时候,先将用户传进来的控制器A用一个JPWarpNavigationController(继承与UINavigationController)包装起来成为B,然后再将B用一个JPWarpViewController包装起来成为C,然后用根导航控制器JPNavigationController来Push和Pop。
  • 经过这样包装以后,每一个控制器都拥有一个自己的导航条,所以,用户可以对每个导航条进行定制,比如说透明、颜色、渐变等操作。
  • 之所以要进行这两层包装的原因是苹果默认的规则是导航控制器不能Push和Pop导航控制器。

02、导航栏更新部分实现思路?

实现目标:1.临时关闭Pop手势 2.自定义右滑手势有效区域

  • 系统的interactivePopGestureRecognizer只提供了一个enabled的属性给我们,所以如果想要做更多的事就要想其他的办法。在这种情况下,我选择了自定义一个UIPanGestureRecognizer,替换系统的手势的实现

    // 自定义的pop手势
    -(UIPanGestureRecognizer *)jp_fullscreenPopGestureRecognizer{
      if (!_jp_fullscreenPopGestureRecognizer) {
        _jp_fullscreenPopGestureRecognizer = [UIPanGestureRecognizer new];
        _jp_fullscreenPopGestureRecognizer.maximumNumberOfTouches = 1;
       }
      return _jp_fullscreenPopGestureRecognizer;
    }
    
     -(void)viewDidLoad{
      [super viewDidLoad];
    
      // 彻底隐藏导航栏
      [self setNavigationBarHidden:YES];
    
      // 添加pop手势(懒加载)
      if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.jp_fullscreenPopGestureRecognizer]) {
    
        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.jp_fullscreenPopGestureRecognizer];
    
        // 用自己的手势替换系统的pop
        NSArray *targets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
        id target = [targets.firstObject valueForKey:@"target"];
        SEL action = NSSelectorFromString(@"handleNavigationTransition:");
        self.jp_fullscreenPopGestureRecognizer.delegate = [self jp_popGestureRecognizerDelegate];
        [self.jp_fullscreenPopGestureRecognizer addTarget:target action:action];
    
        // 系统手势置为不可用
        self.interactivePopGestureRecognizer.enabled = NO;
        }
    }
  • 这样我们用自己的手势替代了系统的右滑功能,所以我们可以在自定义的手势代理方法中自定义右滑的特性:

    -(BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer{
      // 根控制器不允许pop
      if (self.navigationController.viewControllers.count <= 1) {
        return NO;
      }
    
      // 当用户禁止的时候,不允许pop
      if (!self.navigationController.jp_fullScreenPopGestureEnabled) {
        return NO;
      }
    
      // 当开始触发的点大于用户指定的最大触发点的时候,禁止pop
      CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
      CGFloat maxAllowedInitialDistance = self.navigationController.jp_interactivePopMaxAllowedInitialDistanceToLeftEdge;
      if (maxAllowedInitialDistance >= 0 && beginningLocation.x > maxAllowedInitialDistance) {
        return NO;
      }
    
      // 正在做过渡动画的时候禁止pop
      if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
        return NO;
      }
    
      // 反向滑动禁止pop
      CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
      if (translation.x <= 0) {
        return NO;
      }
    
      // 暂时关闭pop手势禁止pop
      if (self.closePopForTemporary) {
        return NO;
      }
    
      return YES;
    }

03、自定义TabBar?

  • 自定义的TabBar应该必须满足两点:1.跟随用户滑动而流畅的滑动 2.不需要在viewWillAppear和viewWillDisappear中处理TabBar的显示和隐藏。
  • 要满足以上两点,首先要考虑的是,我们的TabBar的父控件应该是谁?
  • 从以上的分析大概也可以知道如果我们把TabBar添加到导航栏上,我们需要达到的要求就都满足了。
  • 为了方便大家使用,我先定义一个占位容器视图JPLinkContainerView,我把它添加到导航栏上,作为导航栏的子控件,以后使用的时候,只需要往这个占位视图上添加你自己的TabBar控件就可以了。
  • 当我们把联动的JPLinkContainerView添加到导航条上以后,联动视图就超出父控件的范围,不能响应点击事件了。所以首先我们要做的就是自定义一个JPNavigationBar替换掉系统的导航条。借助于KVC,我们可以实现这一点。
    JPNavigationBar *navBar = [[JPNavigationBar alloc]init];
    [self setValue:navBar forKey:@"navigationBar"];
  • 现在我们自定义了导航条,我们就可以在JPNavigationBar中重载hitTest方法,在这个方法中遍历它自身的子控件,找到我们添加的JPLinkContainerView。当点击的点在我们添加的占位容器视图JPLinkContainerView身上的时候,我们就手动把点击事件分发给我们自定义的TabBar子控件上,完成响应者链条的事件传递。

    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
      JPLinkContainerView *linkView;
      for (UIView *subview in self.subviews) {
        if ([subview isKindOfClass:[JPLinkContainerView class]]) {
            linkView = (JPLinkContainerView *)subview;
            break;
        }
      }
    
      CGPoint viewP = [self convertPoint:point toView:linkView];
      if ([linkView pointInside:viewP withEvent:event]) { // 如果点击的点在联动视图linkView上, 就由linkView来响应事件
          return [linkView hitTest:viewP withEvent:event];
      }
      else{
          return  [super hitTest:point withEvent:event]; // 否则, 执行系统默认的做法
      }
    }

04、使用注意点?

1.关于联动底部视图的高度
@property(nonatomic)CGFloat jp_linkViewHeight;

使用时注意 : 如果你下个界面需要有联动底部视图, 你在上个控制器 - (void)pushViewController:animated:方法后面立即把值传给我:

[self.navigationController pushViewController:YourOwnVC animated:YES];
YourOwnVC.navigationController.jp_linkViewHeight = YourHopeHeight;

同时注意 : 这两行代码有逻辑关系,必须先调用push方法,navigationController才会alloc,分配内存地址,才有值。

2.关于联动底部视图
@property(nonatomic)JPLinkContainerView *jp_linkContainerView;

需要添加自定义的视图的时候,只要把自定义的视图添加到这个视图上就可以了
注意 : 如果识别到你当前控制器为UITableViewController的时候, 如果有联动底部视图, 就会自动为你添加jp_linkViewHeight高度的底部额外滚动区域。 但是, 如果你的控制器是UIViewController上添加了UITableView, 那我不会自动为你添加底部额外滚动区域, 需要你自己为UITableView添加contentInset。

05、这个框架用到的知识点?

06、Demo地址?

框架的Github地址在这里[JPNavigationController]。如果我的文章在你实际工作中恰好帮到了你,麻烦你给个小星星,谢谢。更有甚,如果你和我一样热爱开源、热爱分享,或许可以小手一抖,帮我转发给更多朋友看到。



文/NewPan(简书作者)
原文链接:http://www.jianshu.com/p/3ed21414551a
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

posted on 2016-08-09 23:27  &#127774;Bob  阅读(505)  评论(0编辑  收藏  举报

导航