再看返回按钮(适配IOS11)

2017-12-14  本文已影响25人  leonardni

文章结构


1.现有的几种方式
2.最终方式

文章更新记录


2017-01-06日

1. 添加注意事项

一、现有的统一定制方式


从实现原理上来讲只有三种:

第一种:

使用UIBarButtonItem的两个函数setBackButtonBackgroundImagesetBackButtonTitlePositionAdjustment

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启动的时候统一设置。

缺点:
1.IOS11下自定义的图片会向下偏移。
2.这里只是设置了title的偏移使其偏移出屏幕,title所占的空间依然存在。

WX20171216-142041@2x.png

不考虑,建议用下面两种方式。

第二种

使用IOS7系统后提供的backIndicatorImagebackIndicatorTransitionMaskImage属性。

image.png

基类UINavgationController里调用,或者基类控制器里面调用都行。
基于程序的运行效率建议放到基类UINavgationControllerviewDidload里调用,毕竟只要设置一次。

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全局返回按钮和全屏侧滑功能

  1. 当重写了系统的push方法时,系统的侧滑功能就会失效.
  2. 侧滑功能实现原理:当用户使用侧滑功能的时候,系统是通过代理去通知实现侧滑功能,但是如果重写了push,那么代理知道,既然重写了push方法,那么代理就会通知系统不要去实现侧滑功能,这样就使得侧滑功能失效,或者产生bug.
  3. 思路:我们就通过对代理的控制,来实现对侧滑功能的控制
    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)

上一篇下一篇

猜你喜欢

热点阅读