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

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
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

1行代码为每个Controller自定义“TabBar”-b的更多相关文章

  1. 10行代码搞定移动web端自定义tap事件

    发发牢骚 移动web端里摸爬滚打这么久踩了不少坑,有一定移动web端经验的同学一定被click困扰过.我也不列外.一路走来被虐的不行,fastclick.touchend.iscroll什么的都用过, ...

  2. javascript自定义滚动条插件,几行代码的事儿

    在实际项目中,经常由于浏览器自带的滚动条样式太戳,而且在各个浏览器中显示不一样,所以我们不得不去实现自定义的滚动条,今天我就用最少的代码实现了一个自定义滚动条,代码量区区只有几十行,使用起来也非常方便 ...

  3. iOS框架搭建(MVC,自定义TabBar)--微博搭建为例

    项目搭建 1.新建一个微博的项目,去掉屏幕旋转 2.设置屏幕方向-->只有竖向 3.使用代码构建UI,不使用storyboard 4.配置图标AppIcon和LaunchImage 将微博资料的 ...

  4. flutter 自定义tabbar 给tabbar添加背景功能

    flutter 自带的tabbar BottomNavigationBar有长按水波纹效果,不可以添加背景图片功能,如果有这方面的需求,就需要自定义tabbar了 自定义图片 我们使用BottomAp ...

  5. AJ学IOS 之微博项目实战(4)微博自定义tabBar中间的添加按钮

    AJ分享,必须精品 一:效果图 自定义tabBar实现最下面中间的添加按钮 二:思路 首先在自己的tabBarController中把系统的tabBar设置成自己的tabBar(NYTabBar),这 ...

  6. 代码优化实战,3行代码解决了一百个if else!

    事情是这样的,前段时间做代码review的时候,发现项目中有一个方法代码量超鸡儿多,而且大部分都是写的参数校验的代码,得,我们先抓着缕一缕需求先. 产品需求 找到产品要到了需求文档,需求是这样得: e ...

  7. 动态 WebApi 引擎使用教程(3行代码完成动态 WebApi 构建)

    目录 什么是 WebApiEngine? 开源地址 使用方法 使用 [ApiBind] 标签让任何方法变成 WebApi 对 API 进行分类 自定义 API 名称 复制特性 为整个类配置 WebAp ...

  8. IOS第二天-新浪微博 - 添加搜索框,弹出下拉菜单 ,代理的使用 ,HWTabBar.h(自定义TabBar)

    ********HWDiscoverViewController.m(发现) - (void)viewDidLoad { [super viewDidLoad]; // 创建搜索框对象 HWSearc ...

  9. iOS开发之功能模块--关于自定义TabBar条

    只上项目中用到的代码: 1.实现重写TabBar的TabBarItem,然后在中间额外加一个按钮. #import <UIKit/UIKit.h> @interface BikeTabBa ...

随机推荐

  1. the first assignment of software testing

    Github ID:  bzdwdmzjsmff Github address: https://github.com/bzdwdmzjsmff alternative article: Increa ...

  2. uiautomator日志文件转换为xml格式文件

    如果想把uiautomator的日志文件,转换成漂亮的xml文件,那么可以使用automator-log-converter.jar工具, 工具使用方法: 使用工具automator-log-conv ...

  3. 【转】Android自动化测试之MonkeyRunner录制和回放脚本(四)

    测试脚本录制: 方案一: 我们先看看以下monkeyrecoder.py脚本: #Usage: monkeyrunner recorder.py #recorder.py  http://mirror ...

  4. ADO和ADO.NET有什么不同?

    1.一些ADO中常见的类型比如RecordSet在ADO.NET中已经没有了,而且在ADO.NET中也新增了许多在传统ADO中找不到的直接对应的新类型(如数据适配器): 2.传统的ADO主要针对紧密连 ...

  5. Python(2.7.6) 标准日志模块 - Logging Handler

    Python 标准日志模块使用 Handler 控制日志消息写到不同的目的地,如文件.流.邮件.socket 等.除了StreamHandler. FileHandler 和 NullHandler ...

  6. Could not find artifact com.sun:tools:jar:1.5.0解决方法

    可以参照在XP系统下搭建maven环境出的问题 Unable to locate the Javac Compiler in: C:\Program Files\Java\jre6\..\lib\to ...

  7. JavaScript学习笔记(3)——JavaScript与HTML的组合方式

    一.JavaScript可以写在HTML页面内部, 可位于 HTML 的 <body> 或 <head> 部分中,或者同时存在于两个部分中. 通常的做法是把函数放入 <h ...

  8. OC6_类方法

    // // Dog.h // OC6_类方法 // // Created by zhangxueming on 15/6/9. // Copyright (c) 2015年 zhangxueming. ...

  9. 客官,您的 Flask 全家桶请收好

    http://www.factj.com/archives/543.html Flask-AppBuilder          - Simple and rapid Application buil ...

  10. jquery个人笔记

    一.链式操作 <!DOCTYPE html> <html> <head> <title></title> <script src = ...