iOS假装进步收藏iosUI 搭建

如何优雅的使用 iOS 导航栏

2019-06-07  本文已影响403人  TIME_for

iOS 导航栏一个被翻来覆去说的话题,本来是不想写这篇文章的,可是最近在升级工程导航栏的时候,还是有一些自己的感悟。虽说是老生常谈吧,但是一番操作下来,也遇到了一些问题和想到了一些优雅的解决方案,而且这些问题之前确实少被记录或者说没有统一的记录汇总。这儿我会把我这次遇到的问题,以及 iOS 开发中导航栏通用的解决方案都会做一下介绍。在开始之前,我们先看一下 iOS 导航栏在一些 App 之间的表现,从而引出这次讨论的话题。

一些导航栏实例

有问题的 iOS 导航栏实例
优雅的 iOS 导航栏实例

下面一一来说下上面这些 App 的导航栏:

QQ 类似问题的解决方案

QQ 这类导航栏一般实现的代码是这样的:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // 设置当前页面导航栏的样式
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    // 恢复默认导航栏的样式
}

这样写起来是很简单,但是 iOS 的导航栏是整个 UINavigationControllerviewControllers 共用一个 UINavigationBar 所以这样设置在页面切换的时候,就有可能出现上述问题,整体的解决方案分为两个方向。

方案一:为每个 UIViewController 单独创建一个自己的导航栏

这种方案的实现方式是,每个页面都隐藏系统的导航栏,然后自己添加一个 UIView 或者 UINavigationBar 来充当看到的导航栏。这样的好处就是实现一次之后,后期几乎不会有什么问题。缺点就是,对于已有工程改动起来麻烦,而且这种方式也会丢失侧滑的时候导航栏上面元素的一些渐隐动画,小豆苗正式采用了这种方案。当然为了弥补这个缺点,可以去实现一个类似今日头条那样的转场。或者自己去实现类似系统那样的渐隐动画。

方案二:侧滑的时候添加假导航

在侧滑的时候,检测两个页面之间的导航栏的样式是否相同,如果不同,则将系统的导航隐藏或者设置为透明,然后在两个 UIViewController 之上添加对应的假导航来做过渡动效。侧滑完成之后将导航设置回要显示页面导航的样式,并且移除假导航。这个方案的缺点就是需要自己去实现这个效果,当然也可以考虑一些第三方库比如:YPNavigationBarTransition ,优点就是可以完美的实现导航栏的切换,并且对于已有工程代码改动并不会很大,仅仅限于基类的 UINavigationController

美团类似问题的解决方案

美团这类问题出现的原因是,侧滑的时候下面页面导航透明,上面页面导航不透明,导致上面页面 viewy 值并不是从最上边屏幕开始的,而是从导航部分开始的。所以在侧滑的时候,系统做过渡动效添加的阴影线也是从导航部分开始的,就导致看到的阴影线到导航这儿戛然而止。看下图可以更直观的看出这一原因,图中选中的 UIImageView 就是侧滑时候出现的阴影线。

美团这类问题的原因
解决方案

这个问题的解决方案要从 UIViewControlleredgesForExtendedLayout 这个属性说起。这个属性默认为 UIRectEdgeAll,也就是说一般情况下 UIViewController view的大小是全屏幕尺寸的。但是如果你设置这个属性为 UIRectEdgeNoneUIViewController view 的大小就会自动除去导航栏的部分,这样会方便我们布局。但是同样会造成侧滑返回时候的阴影问题。还有一种情况就是,当设置导航栏不透明时候, UIViewController view 的大小也会自动除去导航栏的部分,这个时候系统提供了另一个属性 extendedLayoutIncludesOpaqueBars 来解决这个问题,当 extendedLayoutIncludesOpaqueBars = YES 即使导航栏不透明 UIViewController view 的大小还是全尺寸的,这样侧滑的时候,阴影线就会是整个屏幕高度的,但是我们在写页面布局的时候,就需要注意一下。

在解决这个问题的时候,开始我并没有想用上面的方法,而是想着在侧滑的时候,去获取系统做阴影的 UIImageView 然后去改变这个UIImageViewframe 从而解决该问题。但是我并没有找到一个手段可以获取到对应的这个 UIImageView 从而使我放弃了该方法,如果你有方式获取到这个视图不妨一起来探讨,直接修改视图的这个方案的好处在于不用改动现有工程的代码。对于已有的工程,修改 extendedLayoutIncludesOpaqueBars 属性还是有不少的工作量,我想这也是很多 App 没有修改这个问题的原因之一。

钉钉类似问题的解决方案

钉钉这类问题出现的原因是因为一般我们都是通过设置导航的 leftBarButtonItem 来充当返回按钮。但是在 iOS11 的时候,苹果对导航栏进行了改版设置的 leftBarButtonItem 会被包装进一个叫 _UIButtonBarStackView 中。而这个 View 与屏幕左边的尺寸固定为 16,但是一般我们做 App 的时候,设计又会特别的将返回按钮设置的特别靠左,基本会小于 16,这时候我们一般是通过调整 UIButtoncontentEdgeInsets 调整出这样的效果,这样在正常显示的时候没有问题,但是在侧滑返回的时候,就会出现上述钉钉这样类似的情况。针对这个问题解决方案就是找到 _UIButtonBarStackView 将它的 x 改为从 0 即可。当然侧滑返回的时候,如果 rightBarButtonItem 设置的太靠右也会出现这样的问题,解决方法是一样的。这里给出一个我的解决方案,代码如下:

@interface LeftContainerView : UIView

- (instancetype)initWithCustomView:(UIView *)customView;

@end

- (instancetype)initWithCustomView:(UIView *)customView {
    if (self = [super initWithFrame:customView.bounds]) {
        [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
        [self addSubview:customView];
    }
    
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    [self removeObserver:self forKeyPath:@"frame"];
    if ([keyPath isEqualToString:@"frame"]) {
        if ([self.superview.superview isKindOfClass:NSClassFromString(@"_UIButtonBarStackView")]) {
            self.superview.superview.transform = CGAffineTransformMakeTranslation(-16, 0);
        } else {
            self.frame = self.bounds;
        }
    }
    [self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)dealloc {
    [self removeObserver:self forKeyPath:@"frame"];
}

@end

- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(0, 0, 100, 44);
    button.backgroundColor = [UIColor redColor];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:[[LeftContainerView alloc] initWithCustomView:button]];    
}

这里通过自定义 LeftContainerView 然后在设置 leftBarButtonItem 的时候都使用这个作为 CustomView,这里面已经纠正了偏移的值。当然除了这个方案,还有其他的一些调整 _UIButtonBarStackView 的方案,或者可以和公司设计商量返回按钮不要往左边偏移太多。

自己工程中特有的问题

公司 App 的导航栏还处于 iOS6 升级 iOS7 做的一次改造,方案用的是截图的方案。因为截图会带来一些性能损耗并且这次在做整个 App 大版本升级的时候也出现了一些问题,所以就考虑整体优化下这一块。经过一系列调研,最终我是选择了使用侧滑的时候添加假的导航这种方式来做这次的改版升级,真假导航这块的处理是在导航栏代理方法 willShowdidShow 中去处理,这样工程中本类就有一个基类 UINavigationController 处理起来成本是最小的。在修改导航栏的时候,上面说的问题都有遇到,类似钉钉这样的问题,工程里面的 leftBarButtonItemrightBarButtonItem 本来也是统一处理的,所以改动成本也很小,但是类似美团这样的问题,改动起来就很大了,而我也仅是选取了部分页面去做修改。

另外我们公司的 App 还有一个很致命的问题,就是有着整个 App 级别的新旧切换,这样我在使用新版的时候还不能去动旧版的方式,当然新版的做法旧版所有页面也是完全支持的,但是里面有很多的问题已经不值得在去处理了。关于导航栏新旧兼容,我用了一个很投巧的方式,是在 UINavigationController 里面直接做了处理,针对新旧版,使用不同的处理方式,这样确实省了很多不必要的麻烦。

感悟

其实导航栏的问题并不难,但是纵观市场上的 App 导航栏都有着这样或者那样的问题。我想一来是因为苹果导航栏的 Api 一直在变化,导致大家适配起来都有一定的成本,而且没有出太多的问题,就先不去动它;二来就是开发者缺少一份打造完美导航栏的心态。另外本文也只是记录了自己在做的时候遇到的一些问题,并没有去详细讲解导航栏相关的基本知识,当然还有一些我没有碰到的问题,也欢迎大家在评论区留言一起探讨。

上一篇下一篇

猜你喜欢

热点阅读