iOS11更新后,用Xcode9跑一下本身的项目,发现返回按钮不灵敏了,点击10次只有3-4次点中。这是由于iOS11系统在导航栏里面的布局和控件都变化了,致使图片按钮(UIBarButtonItem中仅放图片的item的简称)的很小,几乎点不到,文字按钮(UIBarButtonItem中仅放文字的item简称)还能够点到。web
我试图用runtime去获取系统的返回item的子视图去从新布局,结果都是私有API,获取不到;然而以前加弹簧解决方法在iOS11上也是行不通的。架构
UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
因此我用了下面的本身想出来的解决方案。ide
下面我来讲一下我本身的完美解决方案。须要添加的代码少于60行,改动的文件为一个。svg
第1、首先看一下个人项目添加返回按钮的位置
我是在BaseNavigationController里面push的时刻,获取被push的VC,而后给VC添加的返回按钮。总体代码以下布局
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{ if (self.viewControllers.count>0) { viewController.hidesBottomBarWhenPushed = YES; BaseViewController *vc = (BaseViewController *)viewController; if (vc.isHideBackItem == YES) { vc.navigationItem.hidesBackButton = YES; }else{ vc.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithIcon:[vc backIconName]?[vc backIconName]:@"arrows_top" highIcon:@"" target:self action:@selector(back:)]; } if ([viewController isKindOfClass:[NSClassFromString(@"LoginViewController") class]]) { for (UIViewController *aVC in self.viewControllers) { if ([aVC isKindOfClass:[NSClassFromString(@"LoginViewController") class]]) { [self popToViewController:aVC animated:YES]; return; } } } }else{ } [super pushViewController:viewController animated:animated]; }
为了方便,我提取出来最关键的代码来解释atom
vc.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithIcon:[vc backIconName]?[vc backIconName]:@"arrows_top" highIcon:@"" target:self action:@selector(back:)];
UIBarButtonItem的itemWithIcon:highIcon:方法是我对UIBarButtonItem的category的方法,下面看UIBarButtonItem的category方法声明,这里主要声明了两个初始化方法,分别针对有图片和文字的,其中文字的无bug,暂时不适配,不须要讲解。code
@interface UIBarButtonItem (addition) + (UIBarButtonItem *)itemWithTitle:(NSString *)title target:(id)target action:(SEL)action; + (UIBarButtonItem *)itemWithIcon:(NSString *)icon highIcon:(NSString *)highIcon target:(id)target action:(SEL)action; @end
前面讲的是个人项目结构,后面要讲的是思路和代码解决方案orm
思路:自定义一个BackView继承UIView,把须要的字控件返回按钮UIButton放到这个自定义的BackView中,最后把BackView传递给UIBarButtonItem的customView,这样无理是iOS几都是完美适配了。不须要从新自定义导航栏,自定义返回按钮等等。咱们依然用系统的UIBarButtonItem,而且改动的代码量很小的状况下解决。xml
代码:(这部分能够直接copy到本身项目中)blog
UIBarButtonItem+addition.h中的代码
#import <UIKit/UIKit.h> @interface BackView:UIView @property(nonatomic,strong)UIButton *btn; @end @interface UIBarButtonItem (addition) + (UIBarButtonItem *)itemWithTitle:(NSString *)title target:(id)target action:(SEL)action; + (UIBarButtonItem *)itemWithIcon:(NSString *)icon highIcon:(NSString *)highIcon target:(id)target action:(SEL)action; @end UIBarButtonItem+addition.m中的代码 @implementation BackView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; } return self; } -(void)layoutSubviews{ [super layoutSubviews]; UINavigationBar *navBar = nil; UIView *aView = self.superview; while (aView) { if ([aView isKindOfClass:[UINavigationBar class]]) { navBar = (UINavigationBar *)aView; break; } aView = aView.superview; } UINavigationItem * navItem = (UINavigationItem *)navBar.items.lastObject; UIBarButtonItem *leftItem = navItem.leftBarButtonItem; UIBarButtonItem *rightItem = navItem.rightBarButtonItem; if (rightItem) {//右边按钮 BackView *backView = rightItem.customView; if ([backView isKindOfClass:self.class]) { backView.btn.x = backView.width -backView.btn.width; } } if (leftItem) {//左边按钮 // BackView *backView = leftItem.customView; } } @end #import "UIBarButtonItem+addition.h" @implementation UIBarButtonItem (addition) + (UIBarButtonItem *)itemWithIcon:(NSString *)icon highIcon:(NSString *)highIcon target:(id)target action:(SEL)action { BackView *customView = [[BackView alloc] initWithFrame:CGRectMake(0, 0, 80, 44)]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:target action:action]; [customView addGestureRecognizer:tap]; customView.btn = [UIButton buttonWithType:UIButtonTypeCustom]; customView.btn.titleLabel.font = [UIFont systemFontOfSize:16.0]; if (icon) { [customView.btn setBackgroundImage:[UIImage imageNamed:icon] forState:UIControlStateNormal]; } if (highIcon) { [customView.btn setBackgroundImage:[UIImage imageNamed:highIcon] forState:UIControlStateHighlighted]; } customView.btn.frame = CGRectMake(0, 0, customView.btn.currentBackgroundImage.size.width, customView.btn.currentBackgroundImage.size.height); customView.btn.centerY = customView.centerY; [customView.btn addTarget:target action:action forControlEvents:UIControlEventTouchUpInside]; [customView addSubview:customView.btn]; return [[UIBarButtonItem alloc] initWithCustomView:customView]; } + (UIBarButtonItem *)itemWithTitle:(NSString *)title target:(id)target action:(SEL)action { UIButton *btn = [[UIButton alloc] init]; [btn setTitle:title forState:UIControlStateNormal]; btn.titleLabel.font = [UIFont systemFontOfSize:16.0]; [btn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled]; [btn setTitleColor:kTintColor forState:UIControlStateNormal]; [btn setTitleColor:kTintColor forState:UIControlStateHighlighted]; [btn addTarget:target action:action forControlEvents:UIControlEventTouchUpInside]; btn.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -15); btn.frame = CGRectMake(0, 0, title.length * 18, 30); return [[UIBarButtonItem alloc] initWithCustomView:btn]; } @end
这里再解释一下,否则有人迷糊,会说为何改了这么多代码,还说改动的代码量很小。
这个分类iOS11适配以前就有的,只是这个方法+ (UIBarButtonItem )itemWithIcon:(NSString )icon highIcon:(NSString *)highIcon target:(id)target action:(SEL)action 里面初始化的为UIButton而不是如今的BackView.
iOS11后个人改动为:在当前文件(由于只有UIBarButtonItem的分类使用这个BackView,因此不必新建文件,这也省很多事情)新建一个BackView类,BackView类的总代码量不多,声明里面为3行
@interface BackView:UIView @property(nonatomic,strong)UIButton *btn; @end
实现文件里面有34行代码,加一块儿不到40行
@implementation BackView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; } return self; } -(void)layoutSubviews{ [super layoutSubviews]; UINavigationBar *navBar = nil; UIView *aView = self.superview; while (aView) { if ([aView isKindOfClass:[UINavigationBar class]]) { navBar = (UINavigationBar *)aView; break; } aView = aView.superview; } UINavigationItem * navItem = (UINavigationItem *)navBar.items.lastObject; UIBarButtonItem *leftItem = navItem.leftBarButtonItem; UIBarButtonItem *rightItem = navItem.rightBarButtonItem; if (rightItem) {//右边按钮 BackView *backView = rightItem.customView; if ([backView isKindOfClass:self.class]) { backView.btn.x = backView.width -backView.btn.width; } } if (leftItem) {//左边按钮 // BackView *backView = leftItem.customView; } } @end
最后改动的就是把分类方法中的UIButton换为BackView中防止UIButton,这样就很巧妙的解决了iOS11返回按钮的问题,若是是BackView在右边有图片按钮,那么可能致使图标的位置很靠左边,因此我在layoutSubviews里面已经作了适配,若是是rightItem,那么我从新布局了BackView里面子控件btn的位置。左按钮用默认的就能够,不用处理。
if (rightItem) {//右边按钮 BackView *backView = rightItem.customView; if ([backView isKindOfClass:self.class]) { backView.btn.x = backView.width -backView.btn.width; } } if (leftItem) {//左边按钮 // BackView *backView = leftItem.customView; }
这里顺便提一个小常识,初始化customView的时候,就是咱们的BackView的时候,frame的size给多少,看个人代码给的是(80,44);
BackView *customView = [[BackView alloc] initWithFrame:CGRectMake(0, 0, 80, 44)];
为何是80,44呢?由于通常的customView的宽度系统都是给的80,因此这里和系统保持一致,而后44应该你们都知道,是导航栏64-状态栏20的结果。
总结,由于是封装的项目架构,适配只须要从底层的一个类文件处理就OK,我这里处理的是UIBarButtonItem+addition分类这一个文件,仅仅动了这个一个文件。一处改动,项目全部的左右按钮都适配到了。体现了封装的好处。