再看返回按钮(适配IOS11)
文章结构
1.现有的几种方式
2.最终方式
文章更新记录
2017-01-06日
1. 添加注意事项
一、现有的统一定制方式
从实现原理上来讲只有三种:
第一种:
使用UIBarButtonItem
的两个函数setBackButtonBackgroundImage
和setBackButtonTitlePositionAdjustment
setBackButtonBackgroundImage
用来设置返回按钮的背景图片
setBackButtonTitlePositionAdjustment
用来设置返回标题的位置,使其偏移出屏幕。
UIImage *backButtonImage = [[UIImage imageNamed:@"nav_icon_back"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 25, 0, 0)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(NSIntegerMin, NSIntegerMin) forBarMetrics:UIBarMetricsDefault];
优点:
1. 可以写在appdelegate里面,app启动的时候统一设置。
WX20171216-142041@2x.png缺点:
1.IOS11下自定义的图片会向下偏移。
2.这里只是设置了title的偏移使其偏移出屏幕,title所占的空间依然存在。
不考虑,建议用下面两种方式。
第二种
使用IOS7系统后提供的backIndicatorImage
和backIndicatorTransitionMaskImage
属性。
基类UINavgationController里调用,或者基类控制器里面调用都行。
基于程序的运行效率建议放到基类UINavgationController
的viewDidload
里调用,毕竟只要设置一次。
BSENavigationController.m
self.navigationController.navigationBar.backIndicatorImage = [UIImage imageNamed:@"backPic"];
self.navigationController.navigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"backPic"];
基类控制器里调用
BSEViewController.m
UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.navigationItem setBackBarButtonItem:backButtonItem];
UIBarButtonItem
属性说明:
ios自定义图片替换系统导航栏返回按钮样式这篇文章种提到一种方式:
在基类UINavgationController里实现UINavigationBarDelegate
方法如下:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item{
UIBarButtonItem *back = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
item.backBarButtonItem = back;
return YES;
}
能够实现,但是有两个潜在的问题:
1.部分二级以后的界面标题重新出现
2.部分界面有一定的概率会出现返回键消失的情况
这里考虑到不是所有控制器都会继承基类,特别是引入的第三方库。所以不采用基类控制器的写法:
直接上代码:
BSENavigationController.h
#import <UIKit/UIKit.h>
#import "UIViewController+WLBackButton.h"
@interface BSENavigationController : UINavigationController
@end
BSENavigationController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self setUpNavitems];
[self setupNavigationBarTheme];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)setUpNavitems{
UIImage *backButtonImage = [[UIImage imageNamed:@"nav_icon_back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
self.navigationBar.backIndicatorImage = backButtonImage;
self.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;
}
#pragma mark - 设置navbar的字体和背景
- (void)setupNavigationBarTheme {
//设置全局navBar字体颜色与背景颜色
UINavigationBar *navBar = [UINavigationBar appearance];
//设置字体颜色
NSDictionary *attrNavBar = @{NSFontAttributeName : [UIFont systemFontOfSize:16.0f], NSForegroundColorAttributeName : HEXCOLOR(0x111111)};
[navBar setTitleTextAttributes:attrNavBar];
//设置背景颜色 首页导航透明
// [navBar lt_setBackgroundColor:[UIColor whiteColor]];
//设置背景颜色一般的方法
[navBar setBarTintColor:[UIColor whiteColor]];
if ([UIDevice currentDevice].systemVersion.integerValue >= 8.0) {
[navBar setTranslucent:NO];
}
}
UIViewController+WLBackButton.h
#import <UIKit/UIKit.h>
@interface UIViewController (WLBackButton)
@end
UIViewController+WLBackButton.m
#import "UIViewController+WLBackButton.h"
#import <objc/runtime.h>
@implementation UIViewController (WLBackButton)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(wl_viewDidLoad);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
-(void)wl_viewDidLoad{
//执行原有方法
[self wl_viewDidLoad];
[self wl_setUpNavItems];
}
-(void)wl_setUpNavItems{
UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
[self.navigationItem setBackBarButtonItem:backButtonItem];
}
ok,完美解决问题。
该方法从原理上来讲只是替换系统原有的返回指示图片,然后在控制器里定义了标题大小为空的backbutton
。不覆盖系统原有的返回事件,也不会导致系统的滑动返回失效。再则部分界面需要定制退出拦截提示时也比较好处理。推荐!!!!
第三种
设置控制器的leftBarButtonItem
//创建一个UIButton
UIButton *backButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 40, 40)];
//设置UIButton的图像
[backButton setImage:[UIImage imageNamed:@"left_select_img.png"] forState:UIControlStateNormal];
//给UIButton绑定一个方法,在这个方法中进行popViewControllerAnimated
[backButton addTarget:self action:@selector(backItemClick) forControlEvents:UIControlEventTouchUpInside];
//然后通过系统给的自定义BarButtonItem的方法创建BarButtonItem
UIBarButtonItem *backItem = [[UIBarButtonItem alloc]initWithCustomView:backButton];
//覆盖返回按键
self.navigationItem.leftBarButtonItem = backItem;
典型的使用方法:
BSENavigationController.m
#pragma mark - 全局返回键以及抽屉事件设置
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
if(self.childViewControllers.count > 0){
//设置返回键
[self setUpBackBtn:viewController];
//隐藏tabbar
viewController.hidesBottomBarWhenPushed = YES;
}
[super pushViewController:viewController animated:animated];
}
#pragma mark - 返回键样式设置
-(void)setUpBackBtn:(UIViewController *)Controller
{
UIButton *LeftItem = [UIButton new];
LeftItem.frame = CGRectMake(0, 0, 25, 35);
[LeftItem.imageView setContentMode:UIViewContentModeLeft];
LeftItem.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
[LeftItem setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
[LeftItem addTarget:self action:@selector(BackBtnClick) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *customLeftItem = [[UIBarButtonItem alloc] initWithCustomView:LeftItem];
Controller.navigationItem.leftBarButtonItem = customLeftItem;
}
-(void)BackBtnClick{
[self popViewControllerAnimated:YES];
}
优点:1.定制比较方便
缺点:1.内容会相对偏右,需要代码调整。
2.如果写在基类导航控制器里面,部分控制器要拦截返回事件就会比较麻烦,写在基类控制器内又会比较局限。
3.这种方法会失去手势滑动返回的功能 要能使用需要特殊的处理才行
既然是再看返回按钮,这种方式采用的比较多。这里下文也会贴出相应的解决办法。
问题一:内容会相对偏右,需要代码调整
UIButton *btnBack = [UIButton new];
[btnBack setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
[btnBack setContentEdgeInsets:UIEdgeInsetsMake(0, -10, 0, 0)];
问题二:部分界面返回拦截问题
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self LVSetUpNavItem];
}
-(void)LVSetUpNavItem{
self.navigationItem.leftBarButtonItem = nil;//移除原有的统一定制的返回键
UIButton *LeftItem = [UIButton new];
LeftItem.frame = CGRectMake(0, 0, 24, 24);
[LeftItem.imageView setContentMode:UIViewContentModeCenter];
[LeftItem setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
[LeftItem setBackgroundImage:[UIImage imageNamed:@"nav_fanhui"] forState:UIControlStateNormal];
[LeftItem addTarget:self action:@selector(LVbackBtnClick) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *customLeftItem = [[UIBarButtonItem alloc] initWithCustomView:LeftItem];
self.navigationItem.leftBarButtonItem = customLeftItem;
}
-(void)LVbackBtnClick{
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
[self.navigationController popViewControllerAnimated:YES];
}
问题三:系统滑动返回事件失效问题
这里工程里面用的是UINavigationController+FDFullscreenPopGesture
这个工具类所以不存在这个问题,这里还是贴出处理代码。感兴趣的朋友们可以自己看下这边文章ios全局返回按钮和全屏侧滑功能。
- 当重写了系统的push方法时,系统的侧滑功能就会失效.
- 侧滑功能实现原理:当用户使用侧滑功能的时候,系统是通过代理去通知实现侧滑功能,但是如果重写了push,那么代理知道,既然重写了push方法,那么代理就会通知系统不要去实现侧滑功能,这样就使得侧滑功能失效,或者产生bug.
- 思路:我们就通过对代理的控制,来实现对侧滑功能的控制
3.1 设置一个属性,用来保存在push方法重写之前的代理
//设置一个属性保存系统的代理
@property (nonatomic, strong) id popDelegate;
//设置一个属性保存系统的代理
@property (nonatomic, strong) id popDelegate;
3.2 在重写的push方法中,让代理等于空—-意思是不让代理去通知系统侧滑功能失效
self.interactivePopGestureRecognizer.delegate = nil;
3.3 在代理方法中,将原来保存的代理属性,赋值给系统,让系统代理保持原来的工作
#pragma mark - 实现代理方法
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//判断控制器是否为根控制器
if (self.childViewControllers.count == 1) {
//将保存的代理赋值回去,让系统保持原来的侧滑功能
self.interactivePopGestureRecognizer.delegate = self.popDelegate;
}
}
三、注意事项
问题1:UIViewController+BackButtonItemTitle
导致标题文字出现
问题:发现部分界面返回标题又出现了,检查所有UIViewController相关分类发现UIViewController+BackButtonItemTitle.m
具体内部实现就不贴出来了,删除即可。
参考文献:
ios全局返回按钮和全屏侧滑功能
改iOS返回按钮的几种方式
iOS拦截导航栏返回按钮事件的正确方式
简书App适配iOS 11
iOS关于设置一个自定义的导航控制器返回按钮
ios自定义图片替换系统导航栏返回按钮样式
Custom back indicator image and iOS 11
SFEmptyBackButton
自定义iOS的Back按钮(backBarButtonItem)和pop交互手势(interactivepopgesturerecognizer)