[贝聊科技]iOS 11 怎样为导航条上的 UIBarButtonItem 设置间距

作者:陈浩  贝聊科技移动开发部  iOS 工程师

本文已发表在个人博客

以前我们常用 fixedSpace 的方式为 UINavigationBar 上的 UIBarButtonItem 设置间距:

UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace 
                                                                                target:nil 
                                                                                action:nil];

negativeSpacer.width = -8;

[self.navigationItem setLeftBarButtonItems:@[negativeSpacer, button] animated:NO];

然而在 iOS 11 下 UIBarButtonItem width 属性不但失效了,UIBarButtonItem 也开始使用 auto layout 布局,对此我们需要设置 UIBarButtonItem 子 view 的约束。除此之外,苹果还修改了 UINavigationBar 的实现。直到 iOS 10 UINavigationBar 都是采用手动布局,所有的子 view 都是直接加在 UINavigationBar 上。但是,从 iOS 11 开始, UINavigationBar 使用了 auto layout 来布局它的内容子 view,并且子 view 加在了 _UINavigationBarContentView 上。

先来看看 iOS 11 下 UINavigationBar 的视图层级:

UINavigationBar
      | _UIBarBackground
      |    | UIImageView
      |    | UIImageView
      | _UINavigationBarLargeTitleView
      |    | UILabel
      | _UINavigationBarContentView 
      |    | _UIButtonBarStackView
      |    |    | _UIButtonBarButton
      |    |    |    | _UIModernBarButton
      |    |    |    |    | UIButtonLabel
      | _UINavigationBarContentView
      |    | _UIButtonBarStackView
      |    |    | _UITAMICAdaptorView // customView
      |    |    |    | UIBarButtonItem
      |    |    |    |    | UIImageView
      |    |    |    |    | UIButtonLabel

通过 View Debug 工具可知,原来是 stackView 限制了 customView 的宽度以及引起了偏移:

contentView |<-fullWidth----------->|
stackView     |<-stackViewWidth->|
customView    |<-reducedWidth--->|

在此次深挖之前,贝聊客户端的开发小哥们由于项目工期紧以及适配 iOS 11 工作量大,暂时是通过设置 UIButtonsetContentEdgeInsets: 来实现的,这在当时看来是以最小的改动完成了适配,直到 iOS 11.2 这个版本的推出,我们发现当侧滑返回时,导航条的返回按钮会被切掉一点角。(这个方法还有个小缺点是点击区域太小了)

img1
img1

碰巧的是,我的 leader 恰好发现了钉钉也有类似的问题。

iOS 11 虽然已经推出好几个月了,这个问题可能还在困扰着部分同行,接下来就讲讲贝聊是如何解决这个问题的。

由于大家知道 fixed space 失效是系统换成了 auto layout 来实现,所以网上的大部分文章也都是修改 constraint。遗憾的是,我谷歌到挺多使用这种方式去修改要获取到 UINavigationBar 的私有子 view,譬如 contentViewbarStackView,再为私有子 view 添加 leading 和 trailing 的约束等。

我并没有尝试这种方法的可行性,因为始终觉得获取私有子 view 的做法比较脆弱,苹果一旦更换实现,程序的健壮性不好保障。但可以确定的是,解决这个问题的思路大致是修改约束,设法摆脱 stackView 的限制。

在 auto layout 中,约束是使用 alignment rectangle 来确定视图的大小和位置。先看看 alignment rectangle 的作用是怎样,下图摘自《iOS Auto Layout Demystified》:

img2
img2
img3
img3

书中对此的说明是,假如设计师给了你张带角标的气泡图片,程序只期望对气泡进行居中,而图片的 frame 却包含了角标部分,这时可以 override alignmentRectForFrame:frameForAlignmentRectUIView 也给出了相对简便的属性 alignmentRectInsets,需要注意的是,一旦设置了 alignmentRectInsets,view 的 frame 就会根据 alignment rectangle 和 alignmentRectInsets 计算出来。

有了以上的概念后,贝聊的修复方法是子类化一个 UIBarButtonItem 的 customView:

@interface BLNavigationItemCustomView: UIView

@property (nonatomic) UIEdgeInsets alignmentRectInsetsOverride;

@end

@implementation BLNavigationItemCustomView

- (UIEdgeInsets)alignmentRectInsets {
    if (UIEdgeInsetsEqualToEdgeInsets(self.alignmentRectInsetsOverride, UIEdgeInsetsZero)) {
        return super.alignmentRectInsets;
    } else {
        return self.alignmentRectInsetsOverride;
    }
    
}

@end

再就是创建 customView 时针对 iOS 11 做特殊处理,以返回按钮为例:

if (@available(iOS 11.0, *)) {
    button.alignmentRectInsetsOverride = UIEdgeInsetsMake(0, offset, 0, -(offset));
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [button.widthAnchor constraintEqualToConstant:buttonWidth].active = YES;
    [button.heightAnchor constraintEqualToConstant:44].active = YES;
                    
}

之所以设置 widthAnchor、heightAnchor 是前文提到的需要对 UIBarButtonItem 子 view 设置约束,我在实现时就遇到了怎么修改 frame 都无法撑大 customView 的情况,后来发现是没设置 widthAnchor。我们接着用 View Debug 来看看实现的效果:

img4
img4

这儿有个问题就是 customView 有小部分超出了 stackView 的 bounds,导致了超出部分无法接收点击。有趣的是,使用 iOS 11 之前 fixed space 添加间距的做法可以减少 stackView 的 margin。

UIBarButtonItem *spacer = [UIBarButtonItem bl_barButtonItemSpacerWithWidth:-(offset)];
self.navigationItem.leftBarButtonItems = @[spacer, barButtonItem];

结合上 fixed space 和 alignmentRectInsets,customView 将不再超出它的父视图:

img5
img5

总之,我们需继承复写 alignmentRectInsetsBLNavigationItemCustomView,然后继续保持之前版本 fixed space 的处理,只针对 iOS 11 为 customView 新增约束,就可使间距问题在新旧系统得以解决。

总结

不客气的说,iOS 11 真的是一个挺难适配的版本,期间我都差点放弃对导航条间隔的适配了,好在最后还是顺利解决了。如果你有更好的方式解决,欢迎赐教。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容