关于iOS导航栏返回按钮问题的解决方法

最近遇到一个关于导航栏返回按钮的问题,因为之前项目里面都是用的系统默认的返回按钮样式所以没有想过要去更改,后来有需要将返回按钮箭头旁边的文字去掉,同时将该返回按钮的点击事件重新定义。一开始尝试自定义按钮然后设置为leftBarButtonItem,但是这样图片可能跟系统自带的不一样,还有就是返回按钮的位置跟系统自带的不一样。后来找了一些资料,发现将文字去掉比较简单,一般做法是控制器中添加如下代码,然后他的下一级控制就有一个只有箭头没有文字返回按钮:

代码如下:

UIBarButtonItem *backBtn = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backBtn;

也可以创建一个根控制器在其中使用上述代码然后让其他控制器继承这个根控制器实现批量操作。但是如果遇到需要自定义该返回的点击事件的时候,在上面方法中添加target与action是不可行的,同时这种做法也会产生一个问题,就是实际的返回按钮点击区域总是比按钮看到的范围大,一般是距离箭头右边30距离都可点击。接下来我就带大家一起了解这些问题产生的原因以及如何更好的解决这些问题。

  首先我们看一下按照上面代码去掉返回按钮文字之后的导航栏视图的结构层次,因为导航栏的视图加载以及初始化跟viewController的view不一样,不能再veiwDidLoad中去观察(viewWillAppear中也不行)要在viewDidLoad中才可以看到完整的导航栏视图结构层次。我们可以在一个有去掉文字的返回按钮控制器的viewDidLoad中打上断点然后在控制台执行:

po [[UIWindow keyWindow] recursiveDescription]
会得到如下输出:

<UIWindow: 0x8d6f970; frame = (0 0; 320 480); autoresize = W+H; gestureRecognizers = <NSArray: 0x8d5dbf0>; layer = <UIWindowLayer: 0x8d717d0>>
 | <UILayoutContainerView: 0x8d7bbf0; frame = (0 0; 320 480); autoresize = W+H; gestureRecognizers = <NSArray: 0x8d78a70>; layer = <CALayer: 0x8d7bcd0>>
 | | <UINavigationTransitionView: 0x8d813f0; frame = (0 0; 320 480); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x8d814d0>>
 | | | <UIViewControllerWrapperView: 0x8d61050; frame = (0 0; 320 480); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x8d88f40>>
 | | | | <UIView: 0x8ab0dc0; frame = (0 0; 320 480); autoresize = RM+BM; layer = <CALayer: 0x8ab0610>>
 | | | | | <_UILayoutGuide: 0x8ab0e20; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x8ab0e90>>
 | | | | | <_UILayoutGuide: 0x8ab1080; frame = (0 480; 0 0); hidden = YES; layer = <CALayer: 0x8ab10f0>>
 | | <UINavigationBar: 0x8d75c40; frame = (0 20; 320 44); opaque = NO; autoresize = W; userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x8d5e750>; layer = <CALayer: 0x8d70f00>>
 | | | <_UINavigationBarBackground: 0x8d59af0; frame = (0 -20; 320 64); opaque = NO; autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x8d549f0>> - (null)
 | | | | <_UIBackdropView: 0x8d7c440; frame = (0 0; 320 64); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <_UIBackdropViewLayer: 0x8d7e7b0>>
 | | | | | <_UIBackdropEffectView: 0x8d7f1c0; frame = (0 0; 320 64); clipsToBounds = YES; opaque = NO; autoresize = W+H; userInteractionEnabled = NO; animations = { filters.colorMatrix.inputColorMatrix=<CABasicAnimation: 0x8ba4490>; }; layer = <CABackdropLayer: 0x8d7f480>>
 | | | | | <UIView: 0x8d7fc80; frame = (0 0; 320 64); hidden = YES; opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x8d7fce0>>
 | | | | <UIImageView: 0x8d67cc0; frame = (0 64; 320 0.5); userInteractionEnabled = NO; layer = <CALayer: 0x8d67d50>> - (null)
 | | | <UINavigationItemView: 0x8ab6400; frame = (124 8; 163 27); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ab6480>>
 | | | | <UILabel: 0x8ab64b0; frame = (0 3; 163 22); text = 'A Story About a Fish'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ab6550>>
 | | | <UINavigationItemButtonView: 0x8ab6c80; frame = (8 6; 41 30); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ab6d60>>
 | | | | <UILabel: 0x8ab6f10; frame = (-981 -995; 91 22); text = ''; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ab6fb0>>
 | | | <_UINavigationBarBackIndicatorView: 0x8d87560; frame = (8 12; 12.5 20.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8d87650>> - Back

直接看或许不容易懂,还需要结合Xcode的“Debug view Hierarchy”或者是Reveal工具看实际视图结构:

我们可以看到在UINavigationBar中包含有4个类,它们大致的作用是:

_UINavigationBarBackground :(UIImageView)导航栏背景图(不可以直接设置图片)
UINavigationItemView :(UIView)包含显示导航栏标题
UINavigationItemButtonView :(UIView)包含显示导航栏左视图(不可移除,更改大小,颜色,可以隐藏,决定了点击区域的大小)
_UINavigationBarBackIndicatorView :(UIImageView)导航栏返回按钮箭头图片(不可以修改图片)
_UINavigationBarBackIndicatorView就是返回按钮的箭头也就是我们需要保留的,UINavigationItemButtonView就是我们需要研究的也是我们前面提到问题的主要原因所在。再次看看这个对象在控制台的输出:

 | | | <UINavigationItemButtonView: 0x8ab6c80; frame = (8 6; 41 30); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ab6d60>>
 | | | | <UILabel: 0x8ab6f10; frame = (-981 -995; 91 22); text = ''; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ab6fb0>>

这个UINavigationItemButtonView应该是系统在这个view的draw方法里就决定frame,修改frame就触发needdisplay重新改变它的frame,因此这个view只会根据其上的label(也就是返回按钮显示的文字)的内容变化而改变宽度其余基本不可变,我们虽然将label的内容设置为空但是这个UINavigationItemButtonView的大小却并没有改变同时点击区域也没有改变。从控制台里的还可看到这个veiw的userInteractionEnabled属性为NO,如果说点击的是这个view,但现在这个view是不可交互的,只能说明是真正响应点击事件的是这个view背后的某个控件(视图结构和代码里都没有发现这个控件)。因此要想解决我之前提到的问题就得利用这个UINavigationItemButtonView,我们可以在viewDidAppear中取得到UINavigationItemButtonView(如果用遍历的方式获取会有一个延迟,因为这个view的位置固定为第三个所以我们就直接获取就可以了),将其userInteractionEnabled设置为yes并且在这个View上添加手势tap事件即可:

UIView *nav_back = [self.navigationController.navigationBar.subviews objectAtIndex:2];
if ([nav_back isKindOfClass:NSClassFromString(@"UINavigationItemButtonView")]) {
  nav_back.userInteractionEnabled = YES;
  // UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backAction:)];
  // [nav_back addGestureRecognizer:tap];
  UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
  backButton.frame = CGRectMake(0, 0, 20, 21);
  [backButton addTarget:self action:@selector(customMethod) forControlEvents:UIControlEventTouchUpInside];
  [nav_back addSubview:backButton];
 }

这样可以改变返回按钮的点击事件,此时想要解决点击范围只能在这个view上添加一个指定大小的按钮了。最后总结出来的解决办法就是创建一个viewController的分类:

UIViewController+CustomBackButton.h文件

#import <UIKit/UIKit.h>

@interface UIViewController (CustomBackButton)

- (void)customNavBackButtonMethod;

@end

UIViewController+CustomBackButton.m文件

#import "UIViewController+CustomBackButton.h"
#import <objc/runtime.h>

@implementation UIViewController (CustomBackButton)

+ (void)load {
 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
  Class class = [self class];

  SEL originalSelector = @selector(viewDidLoad);
  SEL swizzledSelector = @selector(mm_viewDidLoad);

  SEL originalSelector1 = @selector(viewDidAppear:);
  SEL swizzledSelector1 = @selector(mm_viewDidAppear);

  Method originalMethod = class_getInstanceMethod(class, originalSelector);
  Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

  Method originalMethod1 = class_getInstanceMethod(class, originalSelector1);
  Method swizzledMethod1 = class_getInstanceMethod(class, swizzledSelector1);

  BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
  BOOL didAddMethod1 = class_addMethod(class, originalSelector1, method_getImplementation(swizzledMethod1), method_getTypeEncoding(swizzledMethod1));

  if (didAddMethod) {
   class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
  } else {
   method_exchangeImplementations(originalMethod, swizzledMethod);
  }

  if (didAddMethod1) {
   class_replaceMethod(class, swizzledSelector1, method_getImplementation(originalMethod1), method_getTypeEncoding(originalMethod1));
  } else {
   method_exchangeImplementations(originalMethod1, swizzledMethod1);
  }
 });
}

#pragma mark - Method Swizzling

- (void)mm_viewDidLoad {
 [self mm_viewDidLoad];
 UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
 [self.navigationItem setBackBarButtonItem:backButtonItem];
}

- (void)mm_viewDidAppear {
 [self mm_viewDidAppear];
 UIView *nav_back = [self.navigationController.navigationBar.subviews objectAtIndex:2];
 if ([nav_back isKindOfClass:NSClassFromString(@"UINavigationItemButtonView")]) {
  nav_back.userInteractionEnabled = YES;
  // UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backAction:)];
  // [nav_back addGestureRecognizer:tap];
  UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
  backButton.frame = CGRectMake(0, 0, 20, 21);
  [backButton addTarget:self action:@selector(customNavBackButtonMethod) forControlEvents:UIControlEventTouchUpInside];
  [nav_back addSubview:backButton];
 }
}

- (void)customNavBackButtonMethod {
 [self.navigationController popViewControllerAnimated:YES];
}

@end

在项目里面创建完以后就会生效。这里所做的就是在所有的viewController执行viewDidLoad的时候将返回按钮的文字置空,在执行viewDidAppear的时候添加一个自定义的按钮去响应pop事件。如果遇到需要自定义返回事件我在.h将返回事件开放出来,只要在对应的viewDidLoad中复写customNavBackButtonMethod方法就可以在点击返回按钮时执行到你复写的方法中了,好了,是不是感觉很简单呢。

(0)

相关推荐

  • iOS中导航栏pop返回时出现黑块问题的解决方法

    前言 苹果在iOS7以后给导航控制器加了一个Pop手势,只要手指在屏幕边缘滑动,当前的控制器的视图就会随着你的手指移动,当用户松手后,系统会判断手指拖动出来的大小来决定是否要执行控制器的pop操作. 这个想法非常棒,但最近在使用中发现了一些问题,下面话不多说了,来一起看看详细的介绍吧. 问题描述: 导航栏正常从A页面push到B页面,从B页面pop返回A页面时遇到过渡过程中导航栏出现黑块的问题. 如截图所示: 问题原因: A界面导航栏被影藏,B页面的导航栏存在.过渡的时候没有动画. 解决方案:

  • IOS 改变导航栏返回按钮的标题实例详解

    IOS 改变导航栏返回按钮的标题实例详解 前言: 下午又找到了一个新的方法 这个方法不错 暂时没有发现异常的地方. 新写的App中需要使用UINavigationController对各个页面进行导航,但由于第一级页面的title较长,在进入第二级页面后返回按钮leftButtonItem的title就会变得很长,对NavigationBar空间占用很大,而且不美观,于是使用代码对leftButtonItem的title文本进行修改,无论是设置self.navigationItem.leftBa

  • iOS11适配工作及导航栏影藏返回文字的解决方法

    前言 本文主要介绍了关于iOS11适配及导航栏影藏返回文字的解决方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 一.iOS11适配工作 这是一篇 WWDC Session 204 "Updating Your App for iOS 11" 的总结,里面的内容涉及到了产品.设计以及开发需要了解的内容. 在 "iPad" 以及 "iPhone 的 Landscape" 下, UITabBarItem 图片和文字并排排列了,并

  • 关于iOS导航栏返回按钮问题的解决方法

    最近遇到一个关于导航栏返回按钮的问题,因为之前项目里面都是用的系统默认的返回按钮样式所以没有想过要去更改,后来有需要将返回按钮箭头旁边的文字去掉,同时将该返回按钮的点击事件重新定义.一开始尝试自定义按钮然后设置为leftBarButtonItem,但是这样图片可能跟系统自带的不一样,还有就是返回按钮的位置跟系统自带的不一样.后来找了一些资料,发现将文字去掉比较简单,一般做法是控制器中添加如下代码,然后他的下一级控制就有一个只有箭头没有文字返回按钮: 复制代码 代码如下: UIBarButtonI

  • iOS 11 使用两种方法替换(Method Swizzling)去掉导航栏返回按钮的文字

    方法一:设置BarButtonItem的文本样式为透明颜色,代码如下: [[UIBarButtonItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor clearColor]} forState:UIControlStateNormal]; [[UIBarButtonItem appearance] setTitleTextAttributes:@{NSForegroundColorA

  • 关于layui导航栏不展示下拉列表的解决方法

    在使用导航栏时下拉列表不展示 没有下拉效果是这样的 经过修改后就解决了: 具体原因是没有导入:layui/layui.js 我的jsp代码是这样的: <html> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="renderer" content="webkit

  • layui禁用侧边导航栏点击事件的解决方法

    直接上代码吧 //JavaScript代码区域 layui.use(['element', 'laypage'],function(){ var element = layui.element; element.on('nav(test)', function(elem){ $(".layui-nav-tree").find(".layui-nav-child").css("display","contents"); });

  • iOS导航栏控制的一些总结

    前言 许久不写UI,对UI的很多东西都生疏了,最近使用导航栏的各种场景做一些总结. 1.导航栏的显示与隐藏 导航栏的显示与隐藏,分两种情况: 1.从不显示导航栏的页面push到显示导航栏的页面. 2.从显示导航栏的页面Push到不显示导航栏的页面. 注意: 1.如果导航栏不显示时,系统的侧滑返回功能无效. 2.虽然侧滑返回功能无效,但是导航栏的 .interactivePopGestureRecognizer.delegate还是存在的. 针对以上两种情况分别处理,整个Push过程都假设是从A页

  • iOS swift 总结NavigationController出现问题及解决方法

    IOS swift 总结NavigationController出现问题及解决方法 最近用Swift语言做了一些iOS项目,颇有些心得,记下一些深刻的问题造福自己,服务大家 1.以NavigationController做为容器后状态栏的字体颜色就会不在受系统的控制,要在NavigationController中的根ViewController中设置方可生效,代码如下: self.navigationController!.navigationBar.barStyle = UIBarStyle.

  • iOS 导航栏无缝圆滑的隐藏 Navigationbar实例代码

    1.ViewController .m - (void)viewDidLoad { [super viewDidLoad]; self.title = @"隐藏导航栏"; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.backgroundColor = [UIColor lightGrayColor]; button.frame = CGRectMake(10, 100, 60, 30);

  • 原生JS实现顶部导航栏显示按钮+搜索框功能

    根据点击导航栏,搜索框的内容会随之变动 话不多说,先上图 HTML代码段 <div class="bar"> <ul id="menu" class="menu"> <li class="active">搜店</li> <li class="gradient">地址</li> <li class="gradient&qu

  • 如何用uni-app实现顶部导航栏显示按钮和搜索框

    最近公司准备做app,最终决定使用uni-app框架开发,但是当把设计图给我的时候我心里有点没底,因为他的设计图顶部长成这个样子: 因为这个功能在小程序是根本无法实现的,可能受这个影响,我感觉好像实现不了,但是我还是回头看了看文档,才发现,这个功能是可以实现的,只需要在pages.json中做一些配置即可 这个在官方称作app-plus,可以自定义导航区域,具体配置如下: "pages": [ { "path": "pages/index/index&qu

随机推荐